Merge branch 'frontend-core' of github.com:Budibase/budibase into experimental-hbs-caching
This commit is contained in:
commit
a76508c76e
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "1.0.49-alpha.8",
|
||||
"version": "1.0.49-alpha.9",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
|
||||
async function getPermissions(queryToFetch) {
|
||||
if (fetched?._id === queryToFetch?._id) {
|
||||
loaded = true
|
||||
return
|
||||
}
|
||||
fetched = queryToFetch
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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"
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -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",
|
||||
"@spectrum-css/button": "^3.0.3",
|
||||
"@spectrum-css/card": "^3.0.3",
|
||||
"@spectrum-css/divider": "^1.0.3",
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3,12 +3,16 @@ 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, cacheTemplates: false }
|
||||
const defaultOpts = {
|
||||
noHelpers: false,
|
||||
cacheTemplates: false,
|
||||
noEscaping: false,
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to check if the object is valid.
|
||||
|
@ -26,21 +30,28 @@ function testObject(object) {
|
|||
* Creates a HBS template function for a given string, and optionally caches it.
|
||||
*/
|
||||
let templateCache = {}
|
||||
function createTemplate(string, noHelpers, cache) {
|
||||
function createTemplate(string, opts) {
|
||||
opts = { ...defaultOpts, ...opts }
|
||||
|
||||
// Finalising adds a helper, can't do this with no helpers
|
||||
const shouldFinalise = !noHelpers
|
||||
const key = `${string}${shouldFinalise}`
|
||||
const shouldFinalise = !opts.noHelpers
|
||||
const key = `${string}${shouldFinalise}${opts.noEscaping}`
|
||||
|
||||
// Reuse the cached template is possible
|
||||
if (cache && templateCache[key]) {
|
||||
if (opts.cacheTemplates && templateCache[key]) {
|
||||
return templateCache[key]
|
||||
}
|
||||
|
||||
string = processors.preprocess(string, shouldFinalise)
|
||||
|
||||
// Optionally disable built in HBS escaping
|
||||
if (opts.noEscaping) {
|
||||
string = exports.disableEscaping(string)
|
||||
}
|
||||
|
||||
// This does not throw an error when template can't be fulfilled,
|
||||
// have to try correct beforehand
|
||||
const instance = noHelpers ? hbsInstanceNoHelpers : hbsInstance
|
||||
const instance = opts.noHelpers ? hbsInstanceNoHelpers : hbsInstance
|
||||
const template = instance.compile(string, {
|
||||
strict: false,
|
||||
})
|
||||
|
@ -53,7 +64,7 @@ function createTemplate(string, noHelpers, cache) {
|
|||
* @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) => {
|
||||
|
@ -84,7 +95,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) => {
|
||||
|
@ -98,7 +109,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) => {
|
||||
|
@ -119,19 +130,17 @@ 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) => {
|
||||
opts = { ...defaultOpts, ...opts }
|
||||
|
||||
// take a copy of input in case of error
|
||||
// Take a copy of input in case of error
|
||||
const input = string
|
||||
if (typeof string !== "string") {
|
||||
throw "Cannot process non-string types."
|
||||
}
|
||||
try {
|
||||
const template = createTemplate(string, opts.noHelpers, opts.cacheTemplates)
|
||||
const template = createTemplate(string, opts)
|
||||
const now = Math.floor(Date.now() / 1000) * 1000
|
||||
return processors.postprocess(
|
||||
template({
|
||||
|
@ -144,6 +153,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.
|
||||
|
@ -160,7 +187,6 @@ module.exports.makePropSafe = property => {
|
|||
* @returns {boolean} Whether or not the input string is valid.
|
||||
*/
|
||||
module.exports.isValid = (string, opts) => {
|
||||
opts = { ...defaultOpts, ...opts }
|
||||
const validCases = [
|
||||
"string",
|
||||
"number",
|
||||
|
@ -174,7 +200,7 @@ module.exports.isValid = (string, opts) => {
|
|||
// don't really need a real context to check if its valid
|
||||
const context = {}
|
||||
try {
|
||||
const template = createTemplate(string, opts.noHelpers, opts.cache)
|
||||
const template = createTemplate(string, opts)
|
||||
template(context)
|
||||
return true
|
||||
} catch (err) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 }}}")
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -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…
Reference in New Issue