Browse Source

Merge branch 'develop' of github.com:Budibase/budibase into frontend-core

master
Andrew Kingston 5 years ago
parent
commit
18e5be9a06
  1. 2
      lerna.json
  2. 2
      packages/backend-core/package.json
  3. 2
      packages/bbui/package.json
  4. 2
      packages/bbui/src/Form/Core/TextArea.svelte
  5. 4
      packages/bbui/src/Form/Core/TextField.svelte
  6. 10
      packages/builder/package.json
  7. 44
      packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/PlusConfigForm.svelte
  8. 75
      packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte
  9. 1
      packages/builder/src/components/integration/AccessLevelSelect.svelte
  10. 2
      packages/cli/package.json
  11. 58
      packages/client/manifest.json
  12. 8
      packages/client/package.json
  13. 2
      packages/client/src/components/app/forms/LongFormField.svelte
  14. 2
      packages/client/src/components/app/forms/StringField.svelte
  15. 4
      packages/frontend-core/package.json
  16. 8
      packages/server/package.json
  17. 17
      packages/server/scripts/integrations/postgres/init.sql
  18. 15
      packages/server/src/api/controllers/row/ExternalRequest.ts
  19. 69
      packages/server/src/integrations/base/sql.ts
  20. 11
      packages/server/src/threads/query.js
  21. 2
      packages/string-templates/package.json
  22. 3
      packages/string-templates/src/index.cjs
  23. 34
      packages/string-templates/src/index.js
  24. 3
      packages/string-templates/src/index.mjs
  25. 1
      packages/string-templates/src/utilities.js
  26. 20
      packages/string-templates/test/basic.spec.js
  27. 6
      packages/worker/package.json

2
lerna.json

@ -1,5 +1,5 @@
{
"version": "1.0.49-alpha.8",
"version": "1.0.49-alpha.9",
"npmClient": "yarn",
"packages": [
"packages/*"

2
packages/backend-core/package.json

@ -1,6 +1,6 @@
{
"name": "@budibase/backend-core",
"version": "1.0.49-alpha.8",
"version": "1.0.49-alpha.9",
"description": "Budibase backend core libraries used in server and worker",
"main": "src/index.js",
"author": "Budibase",

2
packages/bbui/package.json

@ -1,7 +1,7 @@
{
"name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.",
"version": "1.0.49-alpha.8",
"version": "1.0.49-alpha.9",
"license": "MPL-2.0",
"svelte": "src/index.js",
"module": "dist/bbui.es.js",

2
packages/bbui/src/Form/Core/TextArea.svelte

@ -13,6 +13,7 @@
start: textarea.selectionStart,
end: textarea.selectionEnd,
})
export let align = null
let focus = false
let textarea
@ -46,6 +47,7 @@
bind:this={textarea}
placeholder={placeholder || ""}
class="spectrum-Textfield-input"
style={align ? `text-align: ${align}` : ""}
{disabled}
{id}
on:focus={() => (focus = true)}

4
packages/bbui/src/Form/Core/TextField.svelte

@ -12,6 +12,7 @@
export let updateOnChange = true
export let quiet = false
export let dataCy
export let align
const dispatch = createEventDispatcher()
let focus = false
@ -92,8 +93,9 @@
on:input={onInput}
on:keyup={updateValueOnEnter}
{type}
inputmode={type === "number" ? "decimal" : "text"}
class="spectrum-Textfield-input"
style={align ? `text-align: ${align};` : ""}
inputmode={type === "number" ? "decimal" : "text"}
/>
</div>

10
packages/builder/package.json

@ -1,6 +1,6 @@
{
"name": "@budibase/builder",
"version": "1.0.49-alpha.8",
"version": "1.0.49-alpha.9",
"license": "GPL-3.0",
"private": true,
"scripts": {
@ -64,10 +64,10 @@
}
},
"dependencies": {
"@budibase/bbui": "^1.0.49-alpha.8",
"@budibase/client": "^1.0.49-alpha.8",
"@budibase/frontend-core": "^1.0.49-alpha.8",
"@budibase/string-templates": "^1.0.49-alpha.8",
"@budibase/bbui": "^1.0.49-alpha.9",
"@budibase/client": "^1.0.49-alpha.9",
"@budibase/frontend-core": "^1.0.49-alpha.9",
"@budibase/string-templates": "^1.0.49-alpha.9",
"@sentry/browser": "5.19.1",
"@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1",

44
packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/PlusConfigForm.svelte

@ -188,29 +188,27 @@
{:else}
<Body size="S"><i>No tables found.</i></Body>
{/if}
{#if plusTables?.length !== 0 && integration.relationships}
<Divider size="S" />
<div class="query-header">
<Heading size="S">Relationships</Heading>
<Button primary on:click={openRelationshipModal}>
Define relationship
</Button>
</div>
<Body>
Tell budibase how your tables are related to get even more smart features.
</Body>
{#if relationshipInfo && relationshipInfo.length > 0}
<Table
on:click={({ detail }) => openRelationshipModal(detail.from, detail.to)}
schema={relationshipSchema}
data={relationshipInfo}
allowEditColumns={false}
allowEditRows={false}
allowSelectRows={false}
/>
{:else}
<Body size="S"><i>No relationships configured.</i></Body>
{/if}
<Divider size="S" />
<div class="query-header">
<Heading size="S">Relationships</Heading>
<Button primary on:click={() => openRelationshipModal()}>
Define relationship
</Button>
</div>
<Body>
Tell budibase how your tables are related to get even more smart features.
</Body>
{#if relationshipInfo && relationshipInfo.length > 0}
<Table
on:click={({ detail }) => openRelationshipModal(detail.from, detail.to)}
schema={relationshipSchema}
data={relationshipInfo}
allowEditColumns={false}
allowEditRows={false}
allowSelectRows={false}
/>
{:else}
<Body size="S"><i>No relationships configured.</i></Body>
{/if}
<style>

75
packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte

@ -22,6 +22,10 @@
let originalFromName = fromRelationship.name,
originalToName = toRelationship.name
let fromTable, toTable, through, linkTable, tableOptions
let isManyToMany, isManyToOne, relationshipTypes
let errors, valid
let currentTables = {}
if (fromRelationship && !fromRelationship.relationshipType) {
fromRelationship.relationshipType = RelationshipTypes.MANY_TO_ONE
@ -41,61 +45,52 @@
const touched = writable({})
function checkForErrors(
fromTable,
toTable,
throughTable,
fromRelate,
toRelate
) {
function checkForErrors(fromRelate, toRelate) {
const isMany =
fromRelate.relationshipType === RelationshipTypes.MANY_TO_MANY
const tableNotSet = "Please specify a table"
const errors = {}
const errObj = {}
if ($touched.from && !fromTable) {
errors.from = tableNotSet
errObj.from = tableNotSet
}
if ($touched.to && !toTable) {
errors.to = tableNotSet
errObj.to = tableNotSet
}
if ($touched.through && isMany && !fromRelate.through) {
errors.through = tableNotSet
errObj.through = tableNotSet
}
if ($touched.foreign && !isMany && !fromRelate.fieldName) {
errors.foreign = "Please pick the foreign key"
errObj.foreign = "Please pick the foreign key"
}
const colNotSet = "Please specify a column name"
if ($touched.fromCol && !fromRelate.name) {
errors.fromCol = colNotSet
errObj.fromCol = colNotSet
}
if ($touched.toCol && !toRelate.name) {
errors.toCol = colNotSet
errObj.toCol = colNotSet
}
if ($touched.primary && !fromPrimary) {
errors.primary = "Please pick the primary key"
errObj.primary = "Please pick the primary key"
}
// currently don't support relationships back onto the table itself, needs to relate out
const tableError = "From/to/through tables must be different"
if (fromTable && (fromTable === toTable || fromTable === throughTable)) {
errors.from = tableError
if (fromTable && (fromTable === toTable || fromTable === through)) {
errObj.from = tableError
}
if (toTable && (toTable === fromTable || toTable === throughTable)) {
errors.to = tableError
if (toTable && (toTable === fromTable || toTable === through)) {
errObj.to = tableError
}
if (
throughTable &&
(throughTable === fromTable || throughTable === toTable)
) {
errors.through = tableError
if (through && (through === fromTable || through === toTable)) {
errObj.through = tableError
}
const colError = "Column name cannot be an existing column"
if (inSchema(fromTable, fromRelate.name, originalFromName)) {
errors.fromCol = colError
errObj.fromCol = colError
}
if (inSchema(toTable, toRelate.name, originalToName)) {
errors.toCol = colError
errObj.toCol = colError
}
return errors
errors = errObj
}
let fromPrimary
@ -115,13 +110,7 @@
$: fromTable = plusTables.find(table => table._id === toRelationship?.tableId)
$: toTable = plusTables.find(table => table._id === fromRelationship?.tableId)
$: through = plusTables.find(table => table._id === fromRelationship?.through)
$: errors = checkForErrors(
fromTable,
toTable,
through,
fromRelationship,
toRelationship
)
$: checkForErrors(fromRelationship, toRelationship)
$: valid =
Object.keys(errors).length === 0 && Object.keys($touched).length !== 0
$: linkTable = through || toTable
@ -239,19 +228,19 @@
}
function tableChanged(fromTbl, toTbl) {
if (
(currentTables?.from?._id === fromTbl?._id &&
currentTables?.to?._id === toTbl?._id) ||
originalFromName ||
originalToName
) {
return
}
fromRelationship.name = toTbl?.name || ""
errors.fromCol = ""
toRelationship.name = fromTbl?.name || ""
errors.toCol = ""
if (toTbl || fromTbl) {
checkForErrors(
fromTable,
toTable,
through,
fromRelationship,
toRelationship
)
}
currentTables = { from: fromTbl, to: toTbl }
}
</script>

1
packages/builder/src/components/integration/AccessLevelSelect.svelte

@ -30,6 +30,7 @@
async function getPermissions(queryToFetch) {
if (fetched?._id === queryToFetch?._id) {
loaded = true
return
}
fetched = queryToFetch

2
packages/cli/package.json

@ -1,6 +1,6 @@
{
"name": "@budibase/cli",
"version": "1.0.49-alpha.8",
"version": "1.0.49-alpha.9",
"description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js",
"bin": {

58
packages/client/manifest.json

@ -1942,6 +1942,35 @@
"type": "validation/string",
"label": "Validation",
"key": "validation"
},
{
"type": "select",
"label": "Alignment",
"key": "align",
"defaultValue": "left",
"showInBar": true,
"barStyle": "buttons",
"options": [{
"label": "Left",
"value": "left",
"barIcon": "TextAlignLeft",
"barTitle": "Align left"
}, {
"label": "Center",
"value": "center",
"barIcon": "TextAlignCenter",
"barTitle": "Align center"
}, {
"label": "Right",
"value": "right",
"barIcon": "TextAlignRight",
"barTitle": "Align right"
}, {
"label": "Justify",
"value": "justify",
"barIcon": "TextAlignJustify",
"barTitle": "Justify text"
}]
}
]
},
@ -2373,6 +2402,35 @@
"type": "validation/string",
"label": "Validation",
"key": "validation"
},
{
"type": "select",
"label": "Alignment",
"key": "align",
"defaultValue": "left",
"showInBar": true,
"barStyle": "buttons",
"options": [{
"label": "Left",
"value": "left",
"barIcon": "TextAlignLeft",
"barTitle": "Align left"
}, {
"label": "Center",
"value": "center",
"barIcon": "TextAlignCenter",
"barTitle": "Align center"
}, {
"label": "Right",
"value": "right",
"barIcon": "TextAlignRight",
"barTitle": "Align right"
}, {
"label": "Justify",
"value": "justify",
"barIcon": "TextAlignJustify",
"barTitle": "Justify text"
}]
}
]
},

8
packages/client/package.json

@ -1,6 +1,6 @@
{
"name": "@budibase/client",
"version": "1.0.49-alpha.8",
"version": "1.0.49-alpha.9",
"license": "MPL-2.0",
"module": "dist/budibase-client.js",
"main": "dist/budibase-client.js",
@ -19,9 +19,9 @@
"dev:builder": "rollup -cw"
},
"dependencies": {
"@budibase/bbui": "^1.0.49-alpha.8",
"@budibase/frontend-core": "^1.0.49-alpha.8",
"@budibase/string-templates": "^1.0.49-alpha.8",
"@budibase/bbui": "^1.0.49-alpha.9",
"@budibase/frontend-core": "^1.0.49-alpha.9",
"@budibase/string-templates": "^1.0.49-alpha.9",
"regexparam": "^1.3.0",
"rollup-plugin-polyfill-node": "^0.8.0",
"shortid": "^2.2.15",

2
packages/client/src/components/app/forms/LongFormField.svelte

@ -9,6 +9,7 @@
export let disabled = false
export let validation
export let defaultValue = ""
export let align
let fieldState
let fieldApi
@ -35,6 +36,7 @@
disabled={fieldState.disabled}
error={fieldState.error}
id={fieldState.fieldId}
{align}
{placeholder}
/>
</div>

2
packages/client/src/components/app/forms/StringField.svelte

@ -9,6 +9,7 @@
export let disabled = false
export let validation
export let defaultValue = ""
export let align
let fieldState
let fieldApi
@ -34,6 +35,7 @@
id={fieldState.fieldId}
{placeholder}
{type}
{align}
/>
{/if}
</Field>

4
packages/frontend-core/package.json

@ -1,12 +1,12 @@
{
"name": "@budibase/frontend-core",
"version": "1.0.49-alpha.8",
"version": "1.0.49-alpha.9",
"description": "Budibase frontend core libraries used in builder and client",
"author": "Budibase",
"license": "MPL-2.0",
"svelte": "src/index.js",
"dependencies": {
"@budibase/bbui": "^1.0.49-alpha.8",
"@budibase/bbui": "^1.0.49-alpha.9",
"lodash": "^4.17.21",
"svelte": "^3.46.2"
}

8
packages/server/package.json

@ -1,7 +1,7 @@
{
"name": "@budibase/server",
"email": "hi@budibase.com",
"version": "1.0.49-alpha.8",
"version": "1.0.49-alpha.9",
"description": "Budibase Web Server",
"main": "src/index.ts",
"repository": {
@ -70,9 +70,9 @@
"license": "GPL-3.0",
"dependencies": {
"@apidevtools/swagger-parser": "^10.0.3",
"@budibase/backend-core": "^1.0.49-alpha.8",
"@budibase/client": "^1.0.49-alpha.8",
"@budibase/string-templates": "^1.0.49-alpha.8",
"@budibase/backend-core": "^1.0.49-alpha.9",
"@budibase/client": "^1.0.49-alpha.9",
"@budibase/string-templates": "^1.0.49-alpha.9",
"@bull-board/api": "^3.7.0",
"@bull-board/koa": "^3.7.0",
"@elastic/elasticsearch": "7.10.0",

17
packages/server/scripts/integrations/postgres/init.sql

@ -9,12 +9,16 @@ CREATE TABLE Persons (
);
CREATE TABLE Tasks (
TaskID SERIAL PRIMARY KEY,
PersonID INT,
ExecutorID INT,
QaID INT,
Completed BOOLEAN,
TaskName varchar(255),
CONSTRAINT fkPersons
FOREIGN KEY(PersonID)
REFERENCES Persons(PersonID)
CONSTRAINT fkexecutor
FOREIGN KEY(ExecutorID)
REFERENCES Persons(PersonID),
CONSTRAINT fkqa
FOREIGN KEY(QaID)
REFERENCES Persons(PersonID)
);
CREATE TABLE Products (
ProductID SERIAL PRIMARY KEY,
@ -32,8 +36,9 @@ CREATE TABLE Products_Tasks (
PRIMARY KEY (ProductID, TaskID)
);
INSERT INTO Persons (FirstName, LastName, Address, City) VALUES ('Mike', 'Hughes', '123 Fake Street', 'Belfast');
INSERT INTO Tasks (PersonID, TaskName, Completed) VALUES (1, 'assembling', TRUE);
INSERT INTO Tasks (PersonID, TaskName, Completed) VALUES (1, 'processing', FALSE);
INSERT INTO Persons (FirstName, LastName, Address, City) Values ('John', 'Smith', '64 Updown Road', 'Dublin');
INSERT INTO Tasks (ExecutorID, QaID, TaskName, Completed) VALUES (1, 2, 'assembling', TRUE);
INSERT INTO Tasks (ExecutorID, QaID, TaskName, Completed) VALUES (2, 1, 'processing', FALSE);
INSERT INTO Products (ProductName) VALUES ('Computers');
INSERT INTO Products (ProductName) VALUES ('Laptops');
INSERT INTO Products (ProductName) VALUES ('Chairs');

15
packages/server/src/api/controllers/row/ExternalRequest.ts

@ -184,7 +184,7 @@ module External {
thisRow._id = generateIdForRow(row, table)
thisRow.tableId = table._id
thisRow._rev = "rev"
return thisRow
return processFormulas(table, thisRow)
}
function fixArrayTypes(row: Row, table: Table) {
@ -327,8 +327,12 @@ module External {
* This iterates through the returned rows and works out what elements of the rows
* actually match up to another row (based on primary keys) - this is pretty specific
* to SQL and the way that SQL relationships are returned based on joins.
* This is complicated, but the idea is that when a SQL query returns all the relations
* will be separate rows, with all of the data in each row. We have to decipher what comes
* from where (which tables) and how to convert that into budibase columns.
*/
updateRelationshipColumns(
table: Table,
row: Row,
rows: { [key: string]: Row },
relationships: RelationshipsJson[]
@ -339,6 +343,13 @@ module External {
if (!linkedTable) {
continue
}
const fromColumn = `${table.name}.${relationship.from}`
const toColumn = `${linkedTable.name}.${relationship.to}`
// this is important when working with multiple relationships
// between the same tables, don't want to overlap/multiply the relations
if (!relationship.through && row[fromColumn] !== row[toColumn]) {
continue
}
let linked = basicProcessing(row, linkedTable)
if (!linked._id) {
continue
@ -386,6 +397,7 @@ module External {
// this is a relationship of some sort
if (finalRows[rowId]) {
finalRows = this.updateRelationshipColumns(
table,
row,
finalRows,
relationships
@ -399,6 +411,7 @@ module External {
finalRows[thisRow._id] = thisRow
// do this at end once its been added to the final rows
finalRows = this.updateRelationshipColumns(
table,
row,
finalRows,
relationships

69
packages/server/src/integrations/base/sql.ts

@ -191,29 +191,70 @@ class InternalBuilder {
if (!relationships) {
return query
}
const tableSets: Record<string, [any]> = {}
// aggregate into table sets (all the same to tables)
for (let relationship of relationships) {
const from = relationship.from,
to = relationship.to,
toTable = relationship.tableName
if (!relationship.through) {
const keyObj: { toTable: string; throughTable: string | undefined } = {
toTable: relationship.tableName,
throughTable: undefined,
}
if (relationship.through) {
keyObj.throughTable = relationship.through
}
const key = JSON.stringify(keyObj)
if (tableSets[key]) {
tableSets[key].push(relationship)
} else {
tableSets[key] = [relationship]
}
}
for (let [key, relationships] of Object.entries(tableSets)) {
const { toTable, throughTable } = JSON.parse(key)
if (!throughTable) {
// @ts-ignore
query = query.leftJoin(
query = query.join(
toTable,
`${fromTable}.${from}`,
`${toTable}.${to}`
function () {
for (let relationship of relationships) {
const from = relationship.from,
to = relationship.to
// @ts-ignore
this.orOn(`${fromTable}.${from}`, "=", `${toTable}.${to}`)
}
},
"left"
)
} else {
const throughTable = relationship.through
const fromPrimary = relationship.fromPrimary
const toPrimary = relationship.toPrimary
query = query
// @ts-ignore
.leftJoin(
.join(
throughTable,
`${fromTable}.${fromPrimary}`,
`${throughTable}.${from}`
function () {
for (let relationship of relationships) {
const fromPrimary = relationship.fromPrimary
const from = relationship.from
// @ts-ignore
this.orOn(
`${fromTable}.${fromPrimary}`,
"=",
`${throughTable}.${from}`
)
}
},
"left"
)
.join(
toTable,
function () {
for (let relationship of relationships) {
const toPrimary = relationship.toPrimary
const to = relationship.to
// @ts-ignore
this.orOn(`${toTable}.${toPrimary}`, `${throughTable}.${to}`)
}
},
"left"
)
.leftJoin(toTable, `${toTable}.${toPrimary}`, `${throughTable}.${to}`)
}
}
return query.limit(BASE_LIMIT)

11
packages/server/src/threads/query.js

@ -5,9 +5,6 @@ const { integrations } = require("../integrations")
const { processStringSync } = require("@budibase/string-templates")
const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
const IS_TRIPLE_BRACE = new RegExp(/^{{3}.*}{3}$/)
const IS_HANDLEBARS = new RegExp(/^{{2}.*}{2}$/)
class QueryRunner {
constructor(input, flags = { noRecursiveQuery: false }) {
this.datasource = input.datasource
@ -188,12 +185,8 @@ class QueryRunner {
enrichedQuery[key] = this.enrichQueryFields(fields[key], parameters)
} else if (typeof fields[key] === "string") {
// enrich string value as normal
let value = fields[key]
// add triple brace to avoid escaping e.g. '=' in cookie header
if (IS_HANDLEBARS.test(value) && !IS_TRIPLE_BRACE.test(value)) {
value = `{${value}}`
}
enrichedQuery[key] = processStringSync(value, parameters, {
enrichedQuery[key] = processStringSync(fields[key], parameters, {
noEscaping: true,
noHelpers: true,
})
} else {

2
packages/string-templates/package.json

@ -1,6 +1,6 @@
{
"name": "@budibase/string-templates",
"version": "1.0.49-alpha.8",
"version": "1.0.49-alpha.9",
"description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs",
"module": "dist/bundle.mjs",

3
packages/string-templates/src/index.cjs

@ -17,6 +17,7 @@ module.exports.processString = templates.processString
module.exports.processObject = templates.processObject
module.exports.doesContainStrings = templates.doesContainStrings
module.exports.doesContainString = templates.doesContainString
module.exports.disableEscaping = templates.disableEscaping
/**
* Use vm2 to run JS scripts in a node env
@ -27,4 +28,4 @@ setJSRunner((js, context) => {
timeout: 1000
})
return vm.run(js)
})
})

34
packages/string-templates/src/index.js

@ -3,12 +3,12 @@ const { registerAll } = require("./helpers/index")
const processors = require("./processors")
const { atob, btoa } = require("./utilities")
const manifest = require("../manifest.json")
const { FIND_HBS_REGEX } = require("./utilities")
const { FIND_HBS_REGEX, FIND_DOUBLE_HBS_REGEX } = require("./utilities")
const hbsInstance = handlebars.create()
registerAll(hbsInstance)
const hbsInstanceNoHelpers = handlebars.create()
const defaultOpts = { noHelpers: false }
const defaultOpts = { noHelpers: false, noEscaping: false }
/**
* utility function to check if the object is valid
@ -27,7 +27,7 @@ function testObject(object) {
* @param {object|array} object The input structure which is to be recursed, it is important to note that
* if the structure contains any cycles then this will fail.
* @param {object} context The context that handlebars should fill data from.
* @param {object|null} opts optional - specify some options for processing.
* @param {object|undefined} opts optional - specify some options for processing.
* @returns {Promise<object|array>} The structure input, as fully updated as possible.
*/
module.exports.processObject = async (object, context, opts) => {
@ -58,7 +58,7 @@ module.exports.processObject = async (object, context, opts) => {
* then nothing will occur.
* @param {string} string The template string which is the filled from the context object.
* @param {object} context An object of information which will be used to enrich the string.
* @param {object|null} opts optional - specify some options for processing.
* @param {object|undefined} opts optional - specify some options for processing.
* @returns {Promise<string>} The enriched string, all templates should have been replaced if they can be.
*/
module.exports.processString = async (string, context, opts) => {
@ -72,7 +72,7 @@ module.exports.processString = async (string, context, opts) => {
* @param {object|array} object The input structure which is to be recursed, it is important to note that
* if the structure contains any cycles then this will fail.
* @param {object} context The context that handlebars should fill data from.
* @param {object|null} opts optional - specify some options for processing.
* @param {object|undefined} opts optional - specify some options for processing.
* @returns {object|array} The structure input, as fully updated as possible.
*/
module.exports.processObjectSync = (object, context, opts) => {
@ -93,7 +93,7 @@ module.exports.processObjectSync = (object, context, opts) => {
* then nothing will occur. This is a pure sync call and therefore does not have the full functionality of the async call.
* @param {string} string The template string which is the filled from the context object.
* @param {object} context An object of information which will be used to enrich the string.
* @param {object|null} opts optional - specify some options for processing.
* @param {object|undefined} opts optional - specify some options for processing.
* @returns {string} The enriched string, all templates should have been replaced if they can be.
*/
module.exports.processStringSync = (string, context, opts) => {
@ -110,7 +110,9 @@ module.exports.processStringSync = (string, context, opts) => {
string = processors.preprocess(string, shouldFinalise)
// this does not throw an error when template can't be fulfilled, have to try correct beforehand
const instance = opts.noHelpers ? hbsInstanceNoHelpers : hbsInstance
const template = instance.compile(string, {
const templateString =
opts && opts.noEscaping ? exports.disableEscaping(string) : string
const template = instance.compile(templateString, {
strict: false,
})
const now = Math.floor(Date.now() / 1000) * 1000
@ -125,6 +127,24 @@ module.exports.processStringSync = (string, context, opts) => {
}
}
/**
* By default with expressions like {{ name }} handlebars will escape various
* characters, which can be problematic. To fix this we use the syntax {{{ name }}},
* this function will find any double braces and switch to triple.
* @param string the string to have double HBS statements converted to triple.
*/
module.exports.disableEscaping = string => {
let regexp = new RegExp(FIND_DOUBLE_HBS_REGEX)
const matches = string.match(regexp)
if (matches == null) {
return string
}
for (let match of matches) {
string = string.replace(match, `{${match}}`)
}
return string
}
/**
* Simple utility function which makes sure that a templating property has been wrapped in literal specifiers correctly.
* @param {string} property The property which is to be wrapped.

3
packages/string-templates/src/index.mjs

@ -17,6 +17,7 @@ export const processString = templates.processString
export const processObject = templates.processObject
export const doesContainStrings = templates.doesContainStrings
export const doesContainString = templates.doesContainString
export const disableEscaping = templates.disableEscaping
/**
* Use polyfilled vm to run JS scripts in a browser Env
@ -30,4 +31,4 @@ setJSRunner((js, context) => {
}
vm.createContext(context)
return vm.runInNewContext(js, context, { timeout: 1000 })
})
})

1
packages/string-templates/src/utilities.js

@ -1,6 +1,7 @@
const ALPHA_NUMERIC_REGEX = /^[A-Za-z0-9]+$/g
module.exports.FIND_HBS_REGEX = /{{([^{].*?)}}/g
module.exports.FIND_DOUBLE_HBS_REGEX = /(?<!{){{[^{}]+}}(?!})/g
module.exports.isAlphaNumeric = char => {
return char.match(ALPHA_NUMERIC_REGEX)

20
packages/string-templates/test/basic.spec.js

@ -6,6 +6,7 @@ const {
getManifest,
encodeJSBinding,
doesContainString,
disableEscaping,
} = require("../src/index.cjs")
describe("Test that the string processing works correctly", () => {
@ -176,3 +177,22 @@ describe("check does contain string function", () => {
expect(doesContainString(js, "foo")).toEqual(true)
})
})
describe("check that disabling escaping function works", () => {
it("should work for a single statement", () => {
expect(disableEscaping("{{ name }}")).toEqual("{{{ name }}}")
})
it("should work for two statements", () => {
expect(disableEscaping("{{ name }} welcome to {{ platform }}")).toEqual("{{{ name }}} welcome to {{{ platform }}}")
})
it("shouldn't convert triple braces", () => {
expect(disableEscaping("{{{ name }}}")).toEqual("{{{ name }}}")
})
it("should work with a combination", () => {
expect(disableEscaping("{{ name }} welcome to {{{ platform }}}")).toEqual("{{{ name }}} welcome to {{{ platform }}}")
})
})

6
packages/worker/package.json

@ -1,7 +1,7 @@
{
"name": "@budibase/worker",
"email": "hi@budibase.com",
"version": "1.0.49-alpha.8",
"version": "1.0.49-alpha.9",
"description": "Budibase background service",
"main": "src/index.ts",
"repository": {
@ -34,8 +34,8 @@
"author": "Budibase",
"license": "GPL-3.0",
"dependencies": {
"@budibase/backend-core": "^1.0.49-alpha.8",
"@budibase/string-templates": "^1.0.49-alpha.8",
"@budibase/backend-core": "^1.0.49-alpha.9",
"@budibase/string-templates": "^1.0.49-alpha.9",
"@koa/router": "^8.0.0",
"@sentry/node": "^6.0.0",
"@techpass/passport-openidconnect": "^0.3.0",

Loading…
Cancel
Save