From 3b5f9ed3e26b5b1286efe166528c0acabb2fb642 Mon Sep 17 00:00:00 2001
From: unknown
Date: Mon, 18 Jul 2022 13:34:40 +0900
Subject: [PATCH 001/320] Fix popover min-width to have parents full width
---
packages/bbui/src/Popover/Popover.svelte | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/bbui/src/Popover/Popover.svelte b/packages/bbui/src/Popover/Popover.svelte
index 1017ef71fc..756e5e6a09 100644
--- a/packages/bbui/src/Popover/Popover.svelte
+++ b/packages/bbui/src/Popover/Popover.svelte
@@ -63,7 +63,7 @@
diff --git a/packages/builder/src/components/portal/overview/automation/StatusRenderer.svelte b/packages/builder/src/components/portal/overview/automation/StatusRenderer.svelte
index 5f71041809..a50932c582 100644
--- a/packages/builder/src/components/portal/overview/automation/StatusRenderer.svelte
+++ b/packages/builder/src/components/portal/overview/automation/StatusRenderer.svelte
@@ -3,7 +3,8 @@
export let value
$: isError = !value || value.toLowerCase() === "error"
- $: isStopped = value?.toLowerCase() === "stopped"
+ $: isStoppedError = value?.toLowerCase() === "stopped_error"
+ $: isStopped = value?.toLowerCase() === "stopped" || isStoppedError
$: status = getStatus(isError, isStopped)
function getStatus(error, stopped) {
diff --git a/packages/server/src/threads/automation.ts b/packages/server/src/threads/automation.ts
index ac43874ab1..9a2e3b5100 100644
--- a/packages/server/src/threads/automation.ts
+++ b/packages/server/src/threads/automation.ts
@@ -144,7 +144,15 @@ class Orchestrator {
`CRON disabled due to errors - ${this._appId}/${this._automation._id}`
)
await disableCron(this._repeat?.jobId, this._repeat?.jobKey)
- this.updateExecutionOutput(trigger.id, trigger.stepId, {}, STOPPED_STATUS)
+ this.updateExecutionOutput(
+ trigger.id,
+ trigger.stepId,
+ {},
+ {
+ status: AutomationStatus.STOPPED_ERROR,
+ success: false,
+ }
+ )
await storeLog(automation, this.executionOutput)
return true
}
@@ -183,8 +191,20 @@ class Orchestrator {
updateExecutionOutput(id: string, stepId: string, inputs: any, outputs: any) {
const stepObj = { id, stepId, inputs, outputs }
+ // replacing trigger when disabling CRON
+ if (
+ stepId === CRON_STEP_ID &&
+ outputs.status === AutomationStatus.STOPPED_ERROR
+ ) {
+ this.executionOutput.trigger = stepObj
+ this.executionOutput.steps = [stepObj]
+ return
+ }
// first entry is always the trigger (constructor)
- if (this.executionOutput.steps.length === 0) {
+ if (
+ this.executionOutput.steps.length === 0 ||
+ this.executionOutput.trigger.id === id
+ ) {
this.executionOutput.trigger = stepObj
}
this.executionOutput.steps.push(stepObj)
diff --git a/packages/types/src/documents/app/automation.ts b/packages/types/src/documents/app/automation.ts
index ce72adda92..50562461e4 100644
--- a/packages/types/src/documents/app/automation.ts
+++ b/packages/types/src/documents/app/automation.ts
@@ -31,6 +31,7 @@ export enum AutomationStatus {
SUCCESS = "success",
ERROR = "error",
STOPPED = "stopped",
+ STOPPED_ERROR = "stopped_error",
}
export interface AutomationResults {
From e7010296ae9fc4a273a1f197473eb8d020c0b28e Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Tue, 26 Jul 2022 16:13:41 +0100
Subject: [PATCH 008/320] Fixing issue with test after update.
---
packages/server/src/automations/tests/automation.spec.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/server/src/automations/tests/automation.spec.js b/packages/server/src/automations/tests/automation.spec.js
index e7496fa4b3..168819daa3 100644
--- a/packages/server/src/automations/tests/automation.spec.js
+++ b/packages/server/src/automations/tests/automation.spec.js
@@ -31,7 +31,7 @@ describe("Run through some parts of the automations system", () => {
it("should be able to init in builder", async () => {
await triggers.externalTrigger(basicAutomation(), { a: 1 })
await wait(100)
- expect(thread).toHaveBeenCalled()
+ expect(thread.execute).toHaveBeenCalled()
})
it("should be able to init in prod", async () => {
@@ -52,7 +52,7 @@ describe("Run through some parts of the automations system", () => {
}
})
await wait(100)
- expect(thread).toHaveBeenCalledWith(makePartial({
+ expect(thread.execute).toHaveBeenCalledWith(makePartial({
data: {
event: {
fields: {
From b76000f8282220b6d2c2544e4ace0fc9829ef776 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Wed, 27 Jul 2022 12:55:20 +0100
Subject: [PATCH 009/320] Fix issue showing selected component indicators on
initial load sometimes due to null reference
---
packages/client/src/components/preview/IndicatorSet.svelte | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/client/src/components/preview/IndicatorSet.svelte b/packages/client/src/components/preview/IndicatorSet.svelte
index bbd03aa974..662741d100 100644
--- a/packages/client/src/components/preview/IndicatorSet.svelte
+++ b/packages/client/src/components/preview/IndicatorSet.svelte
@@ -72,6 +72,7 @@
// Sanity limit of 100 active indicators
const children = Array.from(parents)
.map(parent => parent?.children?.[0])
+ .filter(x => x != null)
.slice(0, 100)
// If there aren't any nodes then reset
From 56704b3c204a946986f77f96098d4bf619e98027 Mon Sep 17 00:00:00 2001
From: NEOLPAR
Date: Thu, 28 Jul 2022 10:38:54 +0100
Subject: [PATCH 010/320] added a note to clean cookies when swapping modes
---
docs/CONTRIBUTING.md | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index 531ed05749..7637bbcd44 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -162,7 +162,10 @@ When you are running locally, budibase stores data on disk using docker volumes.
### Development Modes
-A combination of environment variables controls the mode budibase runs in.
+A combination of environment variables controls the mode budibase runs in.
+
+| **NOTE**: You need to clean your browser cookies when you change between different modes.
+
Yarn commands can be used to mimic the different modes as described in the sections below:
#### Self Hosted
From 2b53d0bb4bbd00843670508cabe58d0990d6eb61 Mon Sep 17 00:00:00 2001
From: NEOLPAR
Date: Thu, 28 Jul 2022 10:43:14 +0100
Subject: [PATCH 011/320] fixed broken link
---
docs/CONTRIBUTING.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index 7637bbcd44..2439e01781 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -192,7 +192,7 @@ To enable this mode, use:
yarn mode:account
```
### CI
- An overview of the CI pipelines can be found [here](./workflows/README.md)
+ An overview of the CI pipelines can be found [here](../.github/workflows/README.md)
### Pro
From 51620dfc35410100b908662dd68160ba12b10146 Mon Sep 17 00:00:00 2001
From: NEOLPAR
Date: Thu, 28 Jul 2022 11:13:03 +0100
Subject: [PATCH 012/320] cleaning dependencies before bootstrap to linking pro
version
---
.github/workflows/README.md | 2 +-
docs/CONTRIBUTING.md | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/README.md b/.github/workflows/README.md
index c33665c964..c5c5e9cea3 100644
--- a/.github/workflows/README.md
+++ b/.github/workflows/README.md
@@ -132,7 +132,7 @@ This is done to prevent pro needing to be published prior to CI runs in budiabse
- backend-core lives in the monorepo, so it can't be released independently to be used in pro
- therefore the only option is to pull pro from source and release it as a part of the monorepo release, as if it were a mono package
-The install is performed using the same steps as local development, via the `yarn bootstrap` command, see the [Contributing Guide#Pro](../CONTRIBUTING.md#pro)
+The install is performed using the same steps as local development, via the `yarn clean && yarn bootstrap` command, see the [Contributing Guide#Pro](../CONTRIBUTING.md#pro)
The branch to install pro from can vary depending on ref of the commit that triggered the budibase CI job. This is done to enable branches which have changes in both the monorepo and the pro repo to have their CI pass successfully.
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index 2439e01781..910e789e6b 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -206,7 +206,7 @@ yarn mode:account
Note that only budibase maintainers will be able to access the pro repo.
-The `yarn bootstrap` command can be used to replace the NPM supplied dependency with the local source aware version. This is achieved using the `yarn link` command. To see specifically how dependencies are linked see [scripts/link-dependencies.sh](../scripts/link-dependencies.sh). The same link script is used to link dependencies to account-portal in local dev.
+The `yarn clean && yarn bootstrap` command can be used to replace the NPM supplied dependency with the local source aware version. This is achieved using the `yarn link` command. To see specifically how dependencies are linked see [scripts/link-dependencies.sh](../scripts/link-dependencies.sh). The same link script is used to link dependencies to account-portal in local dev.
### Troubleshooting
From 76ce26c6b5b6aed33a01594e9ce560f63bf4acd2 Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Thu, 28 Jul 2022 11:14:06 +0100
Subject: [PATCH 013/320] Adding more logging to server log for cloud etc.
---
packages/server/src/automations/steps/serverLog.js | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/packages/server/src/automations/steps/serverLog.js b/packages/server/src/automations/steps/serverLog.js
index b88a731d56..19bbc36abf 100644
--- a/packages/server/src/automations/steps/serverLog.js
+++ b/packages/server/src/automations/steps/serverLog.js
@@ -31,15 +31,21 @@ exports.definition = {
type: "boolean",
description: "Whether the action was successful",
},
+ message: {
+ type: "string",
+ description: "What was output",
+ },
},
- required: ["success"],
+ required: ["success", "message"],
},
},
}
exports.run = async function ({ inputs, appId }) {
- console.log(`App ${appId} - ${inputs.text}`)
+ const message = `App ${appId} - ${inputs.text}`
+ console.log(message)
return {
success: true,
+ message,
}
}
From 33e1d6934174b895598c762a0e3c55ee54a9f302 Mon Sep 17 00:00:00 2001
From: NEOLPAR
Date: Thu, 28 Jul 2022 11:21:06 +0100
Subject: [PATCH 014/320] linking to doc instead of symlink to make it readable
on Github
---
.github/workflows/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/README.md b/.github/workflows/README.md
index c5c5e9cea3..e70b3ce39d 100644
--- a/.github/workflows/README.md
+++ b/.github/workflows/README.md
@@ -132,7 +132,7 @@ This is done to prevent pro needing to be published prior to CI runs in budiabse
- backend-core lives in the monorepo, so it can't be released independently to be used in pro
- therefore the only option is to pull pro from source and release it as a part of the monorepo release, as if it were a mono package
-The install is performed using the same steps as local development, via the `yarn clean && yarn bootstrap` command, see the [Contributing Guide#Pro](../CONTRIBUTING.md#pro)
+The install is performed using the same steps as local development, via the `yarn clean && yarn bootstrap` command, see the [Contributing Guide#Pro](../../docs/CONTRIBUTING.md#pro)
The branch to install pro from can vary depending on ref of the commit that triggered the budibase CI job. This is done to enable branches which have changes in both the monorepo and the pro repo to have their CI pass successfully.
From 9194c599509cab23341e43fae1d664d0ac676c1b Mon Sep 17 00:00:00 2001
From: NEOLPAR
Date: Thu, 28 Jul 2022 11:32:24 +0100
Subject: [PATCH 015/320] updated table of contents
---
docs/CONTRIBUTING.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index 910e789e6b..8d38ed73ee 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -4,10 +4,10 @@ From opening a bug report to creating a pull request: every contribution is appr
## Table of contents
-- [Quick start](#quick-start)
-- [Status](#status)
-- [What's included](#whats-included)
-- [Bugs and feature requests](#bugs-and-feature-requests)
+- [Where to start](#not-sure-where-to-start)
+- [Contributor Licence Agreement](#contributor-license-agreement-cla)
+- [Glossary of Terms](#glossary-of-terms)
+- [Contributing to Budibase](#contributing-to-budibase)
## Not Sure Where to Start?
From 20e8c03641faa1a562c43adc2d3d87eb4b14038e Mon Sep 17 00:00:00 2001
From: NEOLPAR
Date: Thu, 28 Jul 2022 11:51:02 +0100
Subject: [PATCH 016/320] troubleshooting M1 processors
---
docs/DEV-SETUP-MACOSX.md | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/docs/DEV-SETUP-MACOSX.md b/docs/DEV-SETUP-MACOSX.md
index 5606fd0d10..c5990e58da 100644
--- a/docs/DEV-SETUP-MACOSX.md
+++ b/docs/DEV-SETUP-MACOSX.md
@@ -4,6 +4,11 @@
Install instructions [here](https://brew.sh/)
+| **NOTE**: If you are working on a M1 Apple Silicon which is running Z shell, you could need to add
+`eval $(/opt/homebrew/bin/brew shellenv)` line to your `.zshrc`. This will make your zsh to find the apps you install
+through brew.
+
+
### Install Node
Budibase requires a recent version of node (14+):
@@ -51,4 +56,7 @@ So this command will actually run the application in dev mode. It creates .env f
The dev version will be available on port 10000 i.e.
-http://127.0.0.1:10000/builder/admin
\ No newline at end of file
+http://127.0.0.1:10000/builder/admin
+
+| **NOTE**: If you are working on a M1 Apple Silicon, you will need to uncomment `# platform: linux/amd64` line in
+[hosting/docker-compose-dev.yaml](../hosting/docker-compose.dev.yaml)
\ No newline at end of file
From 348f7e2d1cab20ede39a12586339e962f414b3f4 Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Thu, 28 Jul 2022 13:39:11 +0100
Subject: [PATCH 017/320] Fixing some issues with automations + lucene
filtering (with string templating on the backend) as well as type coercion in
the query rows action.
---
packages/frontend-core/src/utils/lucene.js | 5 +++-
.../server/src/automations/steps/queryRows.js | 23 ++++++++++++++++++-
2 files changed, 26 insertions(+), 2 deletions(-)
diff --git a/packages/frontend-core/src/utils/lucene.js b/packages/frontend-core/src/utils/lucene.js
index 1001ec26a8..9554c2201a 100644
--- a/packages/frontend-core/src/utils/lucene.js
+++ b/packages/frontend-core/src/utils/lucene.js
@@ -1,6 +1,8 @@
import { Helpers } from "@budibase/bbui"
import { OperatorOptions, SqlNumberTypeRangeMap } from "../constants"
+const HBS_REGEX = /{{([^{].*?)}}/g
+
/**
* Returns the valid operator options for a certain data type
* @param type the data type
@@ -98,6 +100,7 @@ export const buildLuceneQuery = filter => {
if (Array.isArray(filter)) {
filter.forEach(expression => {
let { operator, field, type, value, externalType } = expression
+ const isHbs = typeof value === "string" && value.match(HBS_REGEX)?.length > 0
// Parse all values into correct types
if (type === "datetime" && value) {
value = new Date(value).toISOString()
@@ -105,7 +108,7 @@ export const buildLuceneQuery = filter => {
if (type === "number" && !Array.isArray(value)) {
if (operator === "oneOf") {
value = value.split(",").map(item => parseFloat(item))
- } else {
+ } else if (!isHbs) {
value = parseFloat(value)
}
}
diff --git a/packages/server/src/automations/steps/queryRows.js b/packages/server/src/automations/steps/queryRows.js
index b6d1938995..58e7313dd2 100644
--- a/packages/server/src/automations/steps/queryRows.js
+++ b/packages/server/src/automations/steps/queryRows.js
@@ -82,6 +82,27 @@ async function getTable(appId, tableId) {
return ctx.body
}
+function typeCoercion(filters, table) {
+ if (!filters || !table) {
+ return filters
+ }
+ for (let key of Object.keys(filters)) {
+ if (typeof filters[key] === "object") {
+ for (let [property, value] of Object.entries(filters[key])) {
+ const column = table.schema[property]
+ // convert string inputs
+ if (!column || typeof value !== "string") {
+ continue
+ }
+ if (column.type === FieldTypes.NUMBER) {
+ filters[key][property] = parseFloat(value)
+ }
+ }
+ }
+ }
+ return filters
+}
+
exports.run = async function ({ inputs, appId }) {
const { tableId, filters, sortColumn, sortOrder, limit } = inputs
const table = await getTable(appId, tableId)
@@ -99,7 +120,7 @@ exports.run = async function ({ inputs, appId }) {
sortType,
limit,
sort: sortColumn,
- query: filters || {},
+ query: typeCoercion(filters || {}, table),
// default to ascending, like data tab
sortOrder: sortOrder || SortOrders.ASCENDING,
},
From b20ef0c14655404174dcdec3f82096fb4399c565 Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Thu, 28 Jul 2022 13:40:04 +0100
Subject: [PATCH 018/320] Linting.
---
packages/frontend-core/src/utils/lucene.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/frontend-core/src/utils/lucene.js b/packages/frontend-core/src/utils/lucene.js
index 9554c2201a..29c2ca684a 100644
--- a/packages/frontend-core/src/utils/lucene.js
+++ b/packages/frontend-core/src/utils/lucene.js
@@ -100,7 +100,8 @@ export const buildLuceneQuery = filter => {
if (Array.isArray(filter)) {
filter.forEach(expression => {
let { operator, field, type, value, externalType } = expression
- const isHbs = typeof value === "string" && value.match(HBS_REGEX)?.length > 0
+ const isHbs =
+ typeof value === "string" && value.match(HBS_REGEX)?.length > 0
// Parse all values into correct types
if (type === "datetime" && value) {
value = new Date(value).toISOString()
From 154619c42ab8b7eb71c0871d6d75003cccf8ea4e Mon Sep 17 00:00:00 2001
From: NEOLPAR
Date: Thu, 28 Jul 2022 16:56:50 +0100
Subject: [PATCH 019/320] making link accesible from github instead of symlink
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index e8c6475d90..b969a2128c 100644
--- a/README.md
+++ b/README.md
@@ -164,7 +164,7 @@ If you have a question or would like to talk with other Budibase users and join
## ❗ Code of conduct
-Budibase is dedicated to providing a welcoming, diverse, and harrassment-free experience for everyone. We expect everyone in the Budibase community to abide by our [**Code of Conduct**](https://github.com/Budibase/budibase/blob/HEAD/.github/CODE_OF_CONDUCT.md). Please read it.
+Budibase is dedicated to providing a welcoming, diverse, and harrassment-free experience for everyone. We expect everyone in the Budibase community to abide by our [**Code of Conduct**](https://github.com/Budibase/budibase/blob/HEAD/docs/CODE_OF_CONDUCT.md). Please read it.
From 4e99856186b4e556dfded60529443c29a2e47cfc Mon Sep 17 00:00:00 2001
From: NEOLPAR
Date: Thu, 28 Jul 2022 18:27:28 +0100
Subject: [PATCH 020/320] agree CLA when PR is created by other contributor
---
docs/CONTRIBUTING.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index 8d38ed73ee..fdd054745e 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -32,6 +32,9 @@ All contributors must sign an [Individual Contributor License Agreement](https:/
If contributing on behalf of your company, your company must sign a [Corporate Contributor License Agreement](https://github.com/budibase/budibase/blob/next/.github/cla/corporate-cla.md). If so, please contact us via community@budibase.com.
+If for any reason, your first contribution is in a PR created by other contributor, please just add a comment to the PR
+with the following text to agree our CLA: "I have read the CLA Document and I hereby sign the CLA".
+
## Glossary of Terms
To understand the budibase API, it can be helpful to understand the top level entities that make up Budibase.
From 3d13030aa1e8c3f311668ffd4dc8d62960358e4b Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Thu, 28 Jul 2022 20:20:53 +0100
Subject: [PATCH 021/320] Initial attempt at transpiling HBS to JS.
---
.../common/bindings/BindingPanel.svelte | 29 ++++++-
.../string-templates/src/conversion/index.js | 87 +++++++++++++++++++
.../string-templates/src/helpers/external.js | 3 +
.../string-templates/src/helpers/index.js | 3 +
.../src/helpers/javascript.js | 2 +
packages/string-templates/src/helpers/list.js | 19 ++++
packages/string-templates/src/index.cjs | 1 +
packages/string-templates/src/index.js | 29 +++++++
packages/string-templates/src/index.mjs | 1 +
.../string-templates/test/hbsToJs.spec.js | 84 ++++++++++++++++++
10 files changed, 255 insertions(+), 3 deletions(-)
create mode 100644 packages/string-templates/src/conversion/index.js
create mode 100644 packages/string-templates/src/helpers/list.js
create mode 100644 packages/string-templates/test/hbsToJs.spec.js
diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte
index f05f935226..6f0bc4615a 100644
--- a/packages/builder/src/components/common/bindings/BindingPanel.svelte
+++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte
@@ -8,6 +8,7 @@
Tab,
Body,
Layout,
+ Button,
} from "@budibase/bbui"
import { createEventDispatcher, onMount } from "svelte"
import {
@@ -15,10 +16,14 @@
decodeJSBinding,
encodeJSBinding,
} from "@budibase/string-templates"
- import { readableToRuntimeBinding } from "builderStore/dataBinding"
+ import {
+ readableToRuntimeBinding,
+ runtimeToReadableBinding,
+ } from "builderStore/dataBinding"
import { handlebarsCompletions } from "constants/completions"
import { addHBSBinding, addJSBinding } from "./utils"
import CodeMirrorEditor from "components/common/CodeMirrorEditor.svelte"
+ import { convertToJS } from "@budibase/string-templates"
const dispatch = createEventDispatcher()
@@ -57,6 +62,7 @@
const updateValue = val => {
valid = isValid(readableToRuntimeBinding(bindings, val))
+ console.log(decodeJSBinding(readableToRuntimeBinding(bindings, val)))
if (valid) {
dispatch("change", val)
}
@@ -69,8 +75,8 @@
}
// Adds a data binding to the expression
- const addBinding = binding => {
- if (usingJS) {
+ const addBinding = (binding, { forceJS } = {}) => {
+ if (usingJS || forceJS) {
let js = decodeJSBinding(jsValue)
js = addJSBinding(js, getCaretPosition(), binding.readableBinding)
jsValue = encodeJSBinding(js)
@@ -100,6 +106,16 @@
updateValue(jsValue)
}
+ const convert = () => {
+ const runtime = readableToRuntimeBinding(bindings, hbsValue)
+ console.log(runtime)
+ const runtimeJs = encodeJSBinding(convertToJS(runtime))
+ jsValue = runtimeToReadableBinding(bindings, runtimeJs)
+ hbsValue = null
+ mode = "JavaScript"
+ addBinding("", { forceJS: true })
+ }
+
onMount(() => {
valid = isValid(readableToRuntimeBinding(bindings, value))
})
@@ -172,6 +188,9 @@
for more details.
{/if}
+
+
+
{#if allowJS}
@@ -306,4 +325,8 @@
color: var(--red);
text-decoration: underline;
}
+
+ .convert {
+ padding-top: var(--spacing-m);
+ }
diff --git a/packages/string-templates/src/conversion/index.js b/packages/string-templates/src/conversion/index.js
new file mode 100644
index 0000000000..62abdf6f54
--- /dev/null
+++ b/packages/string-templates/src/conversion/index.js
@@ -0,0 +1,87 @@
+const { getHelperList } = require("../helpers")
+
+function getLayers(fullBlock) {
+ let layers = []
+ while (fullBlock.length) {
+ const start = fullBlock.lastIndexOf("("),
+ end = fullBlock.indexOf(")")
+ let layer
+ if (start === -1 || end === -1) {
+ layer = fullBlock.trim()
+ fullBlock = ""
+ } else {
+ const untrimmed = fullBlock.substring(start, end + 1)
+ layer = untrimmed.substring(1, untrimmed.length - 1).trim()
+ fullBlock =
+ fullBlock.slice(0, start) +
+ fullBlock.slice(start + untrimmed.length + 1, fullBlock.length)
+ }
+ layers.push(layer)
+ }
+ return layers
+}
+
+function getVariable(variableName) {
+ return isNaN(parseFloat(variableName)) ? `$("${variableName}")` : variableName
+}
+
+function buildList(parts, value) {
+ function build() {
+ return parts
+ .map(part => (part.startsWith("helper") ? part : getVariable(part)))
+ .join(", ")
+ }
+ if (!value) {
+ return parts.length > 1 ? `...[${build()}]` : build()
+ } else {
+ return parts.length === 0 ? value : `...[${value}, ${build()}]`
+ }
+}
+
+function splitBySpace(layer) {
+ const parts = []
+ let started = null,
+ last = 0
+ for (let index = 0; index < layer.length; index++) {
+ const char = layer[index]
+ if (char === "[" && started == null) {
+ started = index
+ } else if (char === "]" && started != null && layer[index + 1] !== ".") {
+ parts.push(layer.substring(started, index + 1).trim())
+ started = null
+ last = index
+ } else if (started == null && char === " ") {
+ parts.push(layer.substring(last, index).trim())
+ last = index
+ }
+ }
+ if (!layer.startsWith("[")) {
+ parts.push(layer.substring(last, layer.length).trim())
+ }
+ return parts
+}
+
+module.exports.convertHBSBlock = (block, blockNumber) => {
+ const braceLength = block[2] === "{" ? 3 : 2
+ block = block.substring(braceLength, block.length - braceLength).trim()
+ const layers = getLayers(block)
+
+ let value = null
+ const list = getHelperList()
+ for (let layer of layers) {
+ const parts = splitBySpace(layer)
+ if (value || parts.length > 1) {
+ // first of layer should always be the helper
+ const helper = parts.splice(0, 1)
+ if (list[helper]) {
+ value = `helpers.${helper}(${buildList(parts, value)})`
+ }
+ }
+ // no helpers
+ else {
+ value = getVariable(parts[0])
+ }
+ }
+ // split by space will remove square brackets
+ return { variable: `var${blockNumber}`, value }
+}
diff --git a/packages/string-templates/src/helpers/external.js b/packages/string-templates/src/helpers/external.js
index 0fa7f734d0..f461045f71 100644
--- a/packages/string-templates/src/helpers/external.js
+++ b/packages/string-templates/src/helpers/external.js
@@ -23,6 +23,9 @@ const ADDED_HELPERS = {
duration: duration,
}
+exports.externalCollections = EXTERNAL_FUNCTION_COLLECTIONS
+exports.addedHelpers = ADDED_HELPERS
+
exports.registerAll = handlebars => {
for (let [name, helper] of Object.entries(ADDED_HELPERS)) {
handlebars.registerHelper(name, helper)
diff --git a/packages/string-templates/src/helpers/index.js b/packages/string-templates/src/helpers/index.js
index 76a4c5d2ca..f04fa58399 100644
--- a/packages/string-templates/src/helpers/index.js
+++ b/packages/string-templates/src/helpers/index.js
@@ -7,6 +7,7 @@ const {
HelperFunctionBuiltin,
LITERAL_MARKER,
} = require("./constants")
+const { getHelperList } = require("./list")
const HTML_SWAPS = {
"<": "<",
@@ -91,3 +92,5 @@ module.exports.unregisterAll = handlebars => {
// unregister all imported helpers
externalHandlebars.unregisterAll(handlebars)
}
+
+module.exports.getHelperList = getHelperList
diff --git a/packages/string-templates/src/helpers/javascript.js b/packages/string-templates/src/helpers/javascript.js
index 0173be0b54..951a9f534a 100644
--- a/packages/string-templates/src/helpers/javascript.js
+++ b/packages/string-templates/src/helpers/javascript.js
@@ -1,6 +1,7 @@
const { atob } = require("../utilities")
const { cloneDeep } = require("lodash/fp")
const { LITERAL_MARKER } = require("../helpers/constants")
+const { getHelperList } = require("./list")
// The method of executing JS scripts depends on the bundle being built.
// This setter is used in the entrypoint (either index.cjs or index.mjs).
@@ -45,6 +46,7 @@ module.exports.processJS = (handlebars, context) => {
// app context.
const sandboxContext = {
$: path => getContextValue(path, cloneDeep(context)),
+ helpers: getHelperList(),
}
// Create a sandbox with our context and run the JS
diff --git a/packages/string-templates/src/helpers/list.js b/packages/string-templates/src/helpers/list.js
new file mode 100644
index 0000000000..a309b9e57f
--- /dev/null
+++ b/packages/string-templates/src/helpers/list.js
@@ -0,0 +1,19 @@
+const externalHandlebars = require("./external")
+const helperList = require("@budibase/handlebars-helpers")
+
+module.exports.getHelperList = () => {
+ let constructed = []
+ for (let collection of externalHandlebars.externalCollections) {
+ constructed.push(helperList[collection]())
+ }
+ const fullMap = {}
+ for (let collection of constructed) {
+ for (let [key, func] of Object.entries(collection)) {
+ fullMap[key] = func
+ }
+ }
+ for (let key of Object.keys(externalHandlebars.addedHelpers)) {
+ fullMap[key] = externalHandlebars.addedHelpers[key]
+ }
+ return fullMap
+}
diff --git a/packages/string-templates/src/index.cjs b/packages/string-templates/src/index.cjs
index d0de680aca..870e14493a 100644
--- a/packages/string-templates/src/index.cjs
+++ b/packages/string-templates/src/index.cjs
@@ -19,6 +19,7 @@ module.exports.doesContainStrings = templates.doesContainStrings
module.exports.doesContainString = templates.doesContainString
module.exports.disableEscaping = templates.disableEscaping
module.exports.findHBSBlocks = templates.findHBSBlocks
+module.exports.convertToJS = templates.convertToJS
/**
* Use vm2 to run JS scripts in a node env
diff --git a/packages/string-templates/src/index.js b/packages/string-templates/src/index.js
index f4feceac4b..eae545de14 100644
--- a/packages/string-templates/src/index.js
+++ b/packages/string-templates/src/index.js
@@ -8,6 +8,7 @@ const {
FIND_ANY_HBS_REGEX,
findDoubleHbsInstances,
} = require("./utilities")
+const { convertHBSBlock } = require("./conversion")
const hbsInstance = handlebars.create()
registerAll(hbsInstance)
@@ -342,3 +343,31 @@ module.exports.findHBSBlocks = string => {
module.exports.doesContainString = (template, string) => {
return exports.doesContainStrings(template, [string])
}
+
+module.exports.convertToJS = hbs => {
+ const blocks = exports.findHBSBlocks(hbs)
+ let js = "return `",
+ prevBlock = null
+ const variables = {}
+ if (blocks.length === 0) {
+ js += hbs
+ }
+ let count = 1
+ for (let block of blocks) {
+ let stringPart = hbs
+ if (prevBlock) {
+ stringPart = stringPart.split(prevBlock)[1]
+ }
+ stringPart = stringPart.split(block)[0]
+ prevBlock = block
+ const { variable, value } = convertHBSBlock(block, count++)
+ variables[variable] = value
+ js += `${stringPart.split()}\${${variable}}`
+ }
+ let varBlock = ""
+ for (let [variable, value] of Object.entries(variables)) {
+ varBlock += `const ${variable} = ${value};\n`
+ }
+ js += "`;"
+ return `${varBlock}${js}`
+}
diff --git a/packages/string-templates/src/index.mjs b/packages/string-templates/src/index.mjs
index 3d115cdec1..34cb90ea34 100644
--- a/packages/string-templates/src/index.mjs
+++ b/packages/string-templates/src/index.mjs
@@ -19,6 +19,7 @@ export const doesContainStrings = templates.doesContainStrings
export const doesContainString = templates.doesContainString
export const disableEscaping = templates.disableEscaping
export const findHBSBlocks = templates.findHBSBlocks
+export const convertToJS = templates.convertToJS
/**
* Use polyfilled vm to run JS scripts in a browser Env
diff --git a/packages/string-templates/test/hbsToJs.spec.js b/packages/string-templates/test/hbsToJs.spec.js
new file mode 100644
index 0000000000..1197907b29
--- /dev/null
+++ b/packages/string-templates/test/hbsToJs.spec.js
@@ -0,0 +1,84 @@
+const {
+ convertToJS
+} = require("../src/index.cjs")
+
+function checkLines(response, lines) {
+ const toCheck = response.split("\n")
+ let count = 0
+ for (let line of lines) {
+ expect(toCheck[count++]).toBe(line)
+ }
+}
+
+describe("Test that the string processing works correctly", () => {
+ it("should convert string without HBS", () => {
+ const response = convertToJS("Hello my name is Michael")
+ expect(response).toBe("return `Hello my name is Michael`;")
+ })
+
+ it("basic example with square brackets", () => {
+ const response = convertToJS("{{ [query] }}")
+ checkLines(response, [
+ "const var1 = $(\"[query]\");",
+ "return `${var1}`;",
+ ])
+ })
+
+ it("should convert some basic HBS strings", () => {
+ const response = convertToJS("Hello {{ name }}, welcome to {{ company }}!")
+ checkLines(response, [
+ "const var1 = $(\"name\");",
+ "const var2 = $(\"company\");",
+ "return `Hello ${var1}, welcome to ${var2}`;",
+ ])
+ })
+
+ it("should handle a helper block", () => {
+ const response = convertToJS("This is the average: {{ avg array }}")
+ checkLines(response, [
+ "const var1 = helpers.avg($(\"array\"));",
+ "return `This is the average: ${var1}`;",
+ ])
+ })
+
+ it("should handle multi-variable helper", () => {
+ const response = convertToJS("This is the average: {{ join ( avg val1 val2 val3 ) }}")
+ checkLines(response, [
+ "const var1 = helpers.join(helpers.avg(...[$(\"val1\"), $(\"val2\"), $(\"val3\")]));",
+ "return `This is the average: ${var1}`;",
+ ])
+ })
+
+ it("should handle a complex statement", () => {
+ const response = convertToJS("This is the average: {{ join ( avg val1 val2 val3 ) val4 }}")
+ checkLines(response, [
+ "const var1 = helpers.join(...[helpers.avg(...[$(\"val1\"), $(\"val2\"), $(\"val3\")]), $(\"val4\")]);",
+ "return `This is the average: ${var1}`;",
+ ])
+ })
+
+ it("should handle square brackets", () => {
+ const response = convertToJS("This is: {{ [val thing] }}")
+ checkLines(response, [
+ "const var1 = $(\"[val thing]\");",
+ "return `This is: ${var1}`;",
+ ])
+ })
+
+ it("should handle square brackets with properties", () => {
+ const response = convertToJS("{{ [user].[_id] }}")
+ checkLines(response, [
+ "const var1 = $(\"[user].[_id]\");",
+ "return `${var1}`;",
+ ])
+ })
+
+ it("should handle multiple complex statements", () => {
+ const response = convertToJS("average: {{ avg ( abs val1 ) val2 }} add: {{ add 1 2 }}")
+ checkLines(response, [
+ "const var1 = helpers.avg(...[helpers.abs($(\"val1\")), $(\"val2\")]);",
+ "const var2 = helpers.add(...[1, 2]);",
+ "return `average: ${var1} add: ${var2}`;",
+ ])
+ })
+})
\ No newline at end of file
From 011b97d57fda9643ba325ee7a1fd6681abb66448 Mon Sep 17 00:00:00 2001
From: NEOLPAR
Date: Thu, 28 Jul 2022 20:35:43 +0100
Subject: [PATCH 022/320] important note for pro modifications
---
.github/workflows/README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/workflows/README.md b/.github/workflows/README.md
index e70b3ce39d..9367ce7073 100644
--- a/.github/workflows/README.md
+++ b/.github/workflows/README.md
@@ -119,6 +119,8 @@ This job is responsible for deploying to our production, cloud kubernetes enviro
## Pro
+| **NOTE**: When developing for both pro / budibase repositories, your branch names need to match, or else the correct pro image doesn't get run within your CI job.
+
### Installing Pro
The pro package is always installed from source in our CI jobs.
From 7a6efe3ea8403960d7c27cb9f9d6ae17b60cd5c6 Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Fri, 29 Jul 2022 09:50:53 +0100
Subject: [PATCH 023/320] Some minor fixes for edge cases.
---
.../common/bindings/BindingPanel.svelte | 2 +-
.../string-templates/src/conversion/index.js | 11 +++++----
.../string-templates/test/hbsToJs.spec.js | 24 +++++++++++++++----
3 files changed, 28 insertions(+), 9 deletions(-)
diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte
index 6f0bc4615a..2a9a64b455 100644
--- a/packages/builder/src/components/common/bindings/BindingPanel.svelte
+++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte
@@ -62,7 +62,7 @@
const updateValue = val => {
valid = isValid(readableToRuntimeBinding(bindings, val))
- console.log(decodeJSBinding(readableToRuntimeBinding(bindings, val)))
+ console.log(readableToRuntimeBinding(bindings, val))
if (valid) {
dispatch("change", val)
}
diff --git a/packages/string-templates/src/conversion/index.js b/packages/string-templates/src/conversion/index.js
index 62abdf6f54..51b760b8c5 100644
--- a/packages/string-templates/src/conversion/index.js
+++ b/packages/string-templates/src/conversion/index.js
@@ -32,9 +32,9 @@ function buildList(parts, value) {
.join(", ")
}
if (!value) {
- return parts.length > 1 ? `...[${build()}]` : build()
+ return parts.length > 1 ? `${build()}` : build()
} else {
- return parts.length === 0 ? value : `...[${value}, ${build()}]`
+ return parts.length === 0 ? value : `${value}, ${build()}`
}
}
@@ -50,12 +50,15 @@ function splitBySpace(layer) {
parts.push(layer.substring(started, index + 1).trim())
started = null
last = index
- } else if (started == null && char === " ") {
+ } else if (started == null && char === " " && last !== index - 1) {
parts.push(layer.substring(last, index).trim())
last = index
}
}
- if (!layer.startsWith("[")) {
+ if (
+ (!layer.startsWith("[") || parts.length === 0) &&
+ last !== layer.length - 1
+ ) {
parts.push(layer.substring(last, layer.length).trim())
}
return parts
diff --git a/packages/string-templates/test/hbsToJs.spec.js b/packages/string-templates/test/hbsToJs.spec.js
index 1197907b29..aca2e931fc 100644
--- a/packages/string-templates/test/hbsToJs.spec.js
+++ b/packages/string-templates/test/hbsToJs.spec.js
@@ -24,6 +24,14 @@ describe("Test that the string processing works correctly", () => {
])
})
+ it("handle properties", () => {
+ const response = convertToJS("{{ [query].id }}")
+ checkLines(response, [
+ "const var1 = $(\"[query].id\");",
+ "return `${var1}`;",
+ ])
+ })
+
it("should convert some basic HBS strings", () => {
const response = convertToJS("Hello {{ name }}, welcome to {{ company }}!")
checkLines(response, [
@@ -33,6 +41,14 @@ describe("Test that the string processing works correctly", () => {
])
})
+ it("Should handle many square brackets in helpers", () => {
+ const response = convertToJS("Hello {{ avg [user].[_id] [user].[_rev] }}")
+ checkLines(response, [
+ "const var1 = helpers.avg($(\"[user].[_id]\"), $(\"[user].[_rev]\"));",
+ "return `Hello ${var1}`;"
+ ])
+ })
+
it("should handle a helper block", () => {
const response = convertToJS("This is the average: {{ avg array }}")
checkLines(response, [
@@ -44,7 +60,7 @@ describe("Test that the string processing works correctly", () => {
it("should handle multi-variable helper", () => {
const response = convertToJS("This is the average: {{ join ( avg val1 val2 val3 ) }}")
checkLines(response, [
- "const var1 = helpers.join(helpers.avg(...[$(\"val1\"), $(\"val2\"), $(\"val3\")]));",
+ "const var1 = helpers.join(helpers.avg($(\"val1\"), $(\"val2\"), $(\"val3\")));",
"return `This is the average: ${var1}`;",
])
})
@@ -52,7 +68,7 @@ describe("Test that the string processing works correctly", () => {
it("should handle a complex statement", () => {
const response = convertToJS("This is the average: {{ join ( avg val1 val2 val3 ) val4 }}")
checkLines(response, [
- "const var1 = helpers.join(...[helpers.avg(...[$(\"val1\"), $(\"val2\"), $(\"val3\")]), $(\"val4\")]);",
+ "const var1 = helpers.join(helpers.avg($(\"val1\"), $(\"val2\"), $(\"val3\")), $(\"val4\"));",
"return `This is the average: ${var1}`;",
])
})
@@ -76,8 +92,8 @@ describe("Test that the string processing works correctly", () => {
it("should handle multiple complex statements", () => {
const response = convertToJS("average: {{ avg ( abs val1 ) val2 }} add: {{ add 1 2 }}")
checkLines(response, [
- "const var1 = helpers.avg(...[helpers.abs($(\"val1\")), $(\"val2\")]);",
- "const var2 = helpers.add(...[1, 2]);",
+ "const var1 = helpers.avg(helpers.abs($(\"val1\")), $(\"val2\"));",
+ "const var2 = helpers.add(1, 2);",
"return `average: ${var1} add: ${var2}`;",
])
})
From 8a77aca54021e0012040652855fd342e194cb012 Mon Sep 17 00:00:00 2001
From: Peter Clement
Date: Fri, 29 Jul 2022 13:10:00 +0100
Subject: [PATCH 024/320] more efficient fetching of total users per app
---
.../overview/_components/AssignmentModal.svelte | 5 ++---
packages/builder/src/stores/portal/users.js | 6 ++++++
packages/frontend-core/src/api/user.js | 11 +++++++++++
packages/worker/src/api/controllers/global/users.ts | 12 +++++++++++-
packages/worker/src/api/routes/global/users.js | 1 +
packages/worker/src/sdk/users/users.ts | 13 +++++++++++--
6 files changed, 42 insertions(+), 6 deletions(-)
diff --git a/packages/builder/src/pages/builder/portal/overview/_components/AssignmentModal.svelte b/packages/builder/src/pages/builder/portal/overview/_components/AssignmentModal.svelte
index aee7a8aa7d..48503e56e6 100644
--- a/packages/builder/src/pages/builder/portal/overview/_components/AssignmentModal.svelte
+++ b/packages/builder/src/pages/builder/portal/overview/_components/AssignmentModal.svelte
@@ -13,7 +13,6 @@
export let app
export let addData
export let appUsers = []
-
let prevSearch = undefined,
search = undefined
let pageInfo = createPaginationStore()
@@ -32,7 +31,7 @@
prevSearch = search
try {
pageInfo.loading()
- await users.search({ page, search })
+ await users.search({ page, email: search })
pageInfo.fetched($users.hasNextPage, $users.nextPage)
} catch (error) {
notifications.error("Error getting user list")
@@ -83,10 +82,10 @@
group.name}
getPrimaryOptionValue={group => group.name}
getPrimaryOptionIcon={group => group.icon}
diff --git a/packages/builder/src/stores/portal/users.js b/packages/builder/src/stores/portal/users.js
index 94fdf806e6..490d1bc9f6 100644
--- a/packages/builder/src/stores/portal/users.js
+++ b/packages/builder/src/stores/portal/users.js
@@ -61,6 +61,7 @@ export function createUsersStore() {
break
case "admin":
body.admin = { global: true }
+ body.builder = { global: true }
break
}
@@ -77,6 +78,10 @@ export function createUsersStore() {
update(users => users.filter(user => user._id !== id))
}
+ async function getUserCountByApp({ appId }) {
+ return await API.getUserCountByApp({ appId })
+ }
+
async function bulkDelete(userIds) {
await API.deleteUsers(userIds)
}
@@ -99,6 +104,7 @@ export function createUsersStore() {
create,
save,
bulkDelete,
+ getUserCountByApp,
delete: del,
}
}
diff --git a/packages/frontend-core/src/api/user.js b/packages/frontend-core/src/api/user.js
index b2ecafdb35..17223a80e6 100644
--- a/packages/frontend-core/src/api/user.js
+++ b/packages/frontend-core/src/api/user.js
@@ -172,4 +172,15 @@ export const buildUserEndpoints = API => ({
},
})
},
+
+ /**
+ * Accepts an invite to join the platform and creates a user.
+ * @param inviteCode the invite code sent in the email
+ * @param password the password for the newly created user
+ */
+ getUserCountByApp: async ({ appId }) => {
+ return await API.get({
+ url: `/api/global/users/count/${appId}`,
+ })
+ },
})
diff --git a/packages/worker/src/api/controllers/global/users.ts b/packages/worker/src/api/controllers/global/users.ts
index ff630efae8..17e655edb3 100644
--- a/packages/worker/src/api/controllers/global/users.ts
+++ b/packages/worker/src/api/controllers/global/users.ts
@@ -3,7 +3,7 @@ import { checkInviteCode } from "../../../utilities/redis"
import { sendEmail } from "../../../utilities/email"
import { users } from "../../../sdk"
import env from "../../../environment"
-import { User, CloudAccount, UserGroup } from "@budibase/types"
+import { User, CloudAccount } from "@budibase/types"
import {
events,
errors,
@@ -114,6 +114,16 @@ export const adminUser = async (ctx: any) => {
})
}
+export const countByApp = async (ctx: any) => {
+ const appId = ctx.params.appId
+ try {
+ const response = await users.countUsersByApp(appId)
+ ctx.body = response
+ } catch (err: any) {
+ ctx.throw(err.status || 400, err)
+ }
+}
+
export const destroy = async (ctx: any) => {
const id = ctx.params.id
diff --git a/packages/worker/src/api/routes/global/users.js b/packages/worker/src/api/routes/global/users.js
index a1aa9fca7f..e62e996443 100644
--- a/packages/worker/src/api/routes/global/users.js
+++ b/packages/worker/src/api/routes/global/users.js
@@ -64,6 +64,7 @@ router
.post("/api/global/users/search", builderOrAdmin, controller.search)
.delete("/api/global/users/:id", adminOnly, controller.destroy)
.post("/api/global/users/bulkDelete", adminOnly, controller.bulkDelete)
+ .get("/api/global/users/count/:appId", adminOnly, controller.countByApp)
.get("/api/global/roles/:appId")
.post(
"/api/global/users/invite",
diff --git a/packages/worker/src/sdk/users/users.ts b/packages/worker/src/sdk/users/users.ts
index 48e4f0db02..d6c667dbfb 100644
--- a/packages/worker/src/sdk/users/users.ts
+++ b/packages/worker/src/sdk/users/users.ts
@@ -20,7 +20,7 @@ import { groups as groupUtils } from "@budibase/pro"
const PAGE_LIMIT = 8
-export const allUsers = async (newDb?: any) => {
+export const allUsers = async () => {
const db = tenancy.getGlobalDB()
const response = await db.allDocs(
dbUtils.getGlobalUserParams(null, {
@@ -30,6 +30,15 @@ export const allUsers = async (newDb?: any) => {
return response.rows.map((row: any) => row.doc)
}
+export const countUsersByApp = async (appId: string) => {
+ let response: any = await usersCore.searchGlobalUsersByApp(appId, {
+ include_docs: true,
+ })
+ return {
+ userCount: response.length,
+ }
+}
+
export const paginatedUsers = async ({
page,
email,
@@ -56,7 +65,7 @@ export const paginatedUsers = async ({
userList = await usersCore.searchGlobalUsersByEmail(email, opts)
property = "email"
} else {
- // no search, query allDocs
+ // no search, query allDcso
const response = await db.allDocs(dbUtils.getGlobalUserParams(null, opts))
userList = response.rows.map((row: any) => row.doc)
}
From 23dc9f612875448958dc83b57b2f01782ad05af7 Mon Sep 17 00:00:00 2001
From: Peter Clement
Date: Fri, 29 Jul 2022 13:13:41 +0100
Subject: [PATCH 025/320] fetching of users via new api in app assignment
---
.../overview/_components/AccessTab.svelte | 32 +++++++++++++------
.../overview/_components/OverviewTab.svelte | 9 ++++--
2 files changed, 29 insertions(+), 12 deletions(-)
diff --git a/packages/builder/src/pages/builder/portal/overview/_components/AccessTab.svelte b/packages/builder/src/pages/builder/portal/overview/_components/AccessTab.svelte
index 65bb90194c..565dfc7aa2 100644
--- a/packages/builder/src/pages/builder/portal/overview/_components/AccessTab.svelte
+++ b/packages/builder/src/pages/builder/portal/overview/_components/AccessTab.svelte
@@ -29,7 +29,6 @@
let pageInfo = createPaginationStore()
let fixedAppId
$: page = $pageInfo.page
- $: fetchUsers(page, search)
$: hasGroupsLicense = $auth.user?.license.features.includes(
Constants.Features.USER_GROUPS
@@ -37,12 +36,6 @@
$: fixedAppId = apps.getProdAppID(app.devId)
- $: appUsers =
- $users.data?.filter(x => {
- return Object.keys(x.roles).find(y => {
- return y === fixedAppId
- })
- }) || []
$: appGroups = $groups.filter(x => {
return x.apps.includes(app.appId)
})
@@ -130,6 +123,12 @@
pageInfo.loading()
await users.search({ page, appId: fixedAppId })
pageInfo.fetched($users.hasNextPage, $users.nextPage)
+ appUsers =
+ $users.data?.filter(x => {
+ return Object.keys(x.roles).find(y => {
+ return y === fixedAppId
+ })
+ }) || []
} catch (error) {
notifications.error("Error getting user list")
}
@@ -137,6 +136,8 @@
onMount(async () => {
try {
+ await fetchUsers(page, search)
+
await groups.actions.init()
await apps.load()
await roles.fetch()
@@ -212,8 +213,14 @@
page={$pageInfo.pageNumber}
hasPrevPage={$pageInfo.loading ? false : $pageInfo.hasPrevPage}
hasNextPage={$pageInfo.loading ? false : $pageInfo.hasNextPage}
- goToPrevPage={pageInfo.prevPage}
- goToNextPage={pageInfo.nextPage}
+ goToPrevPage={async () => {
+ await pageInfo.prevPage()
+ fetchUsers(page, search)
+ }}
+ goToNextPage={async () => {
+ await pageInfo.nextPage()
+ fetchUsers(page, search)
+ }}
/>
{/if}
@@ -264,4 +271,11 @@
justify-content: space-between;
align-items: center;
}
+
+ .pagination {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
+ margin-top: var(--spacing-xl);
+ }
diff --git a/packages/builder/src/pages/builder/portal/overview/_components/OverviewTab.svelte b/packages/builder/src/pages/builder/portal/overview/_components/OverviewTab.svelte
index 6693c285ff..3e8e15fb2c 100644
--- a/packages/builder/src/pages/builder/portal/overview/_components/OverviewTab.svelte
+++ b/packages/builder/src/pages/builder/portal/overview/_components/OverviewTab.svelte
@@ -11,7 +11,7 @@
export let app
export let deployments
export let navigateTab
-
+ let userCount
const dispatch = createEventDispatcher()
const unpublishApp = () => {
@@ -40,7 +40,9 @@
}
onMount(async () => {
- await users.search({ page: undefined, appId: "app_" + app.appId })
+ let resp = await users.getUserCountByApp({ appId: "app_" + app.appId })
+ userCount = resp.userCount
+ await users.search({ appId: "app_" + app.appId, limit: 4 })
})
@@ -155,7 +157,8 @@
- {$users?.data.length} users have access to this app
+ {userCount}
+ {userCount > 1 ? `users have` : `user has`} access to this app
@@ -202,4 +215,16 @@
align-items: center;
grid-template-columns: 1fr 120px 120px 1fr auto auto;
}
+
+ .toggle {
+ display: flex;
+ align-items: center;
+ padding-right: var(--spacing-s);
+ }
+
+ .bottom {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
diff --git a/packages/builder/src/components/design/settings/controls/FilterEditor/FilterEditor.svelte b/packages/builder/src/components/design/settings/controls/FilterEditor/FilterEditor.svelte
index 2cb35a9cf5..ea54afc0ee 100644
--- a/packages/builder/src/components/design/settings/controls/FilterEditor/FilterEditor.svelte
+++ b/packages/builder/src/components/design/settings/controls/FilterEditor/FilterEditor.svelte
@@ -8,21 +8,73 @@
import FilterDrawer from "./FilterDrawer.svelte"
import { currentAsset } from "builderStore"
+ const QUERY_START_REGEX = /\d[0-9]*:/g
const dispatch = createEventDispatcher()
export let value = []
export let componentInstance
export let bindings = []
- let drawer
- let tempValue = value || []
+ let drawer,
+ toSaveFilters = null,
+ allOr,
+ initialAllOr
+ $: initialFilters = correctFilters(value || [])
$: dataSource = getDatasourceForProvider($currentAsset, componentInstance)
$: schema = getSchemaForDatasource($currentAsset, dataSource)?.schema
$: schemaFields = Object.values(schema || {})
- const saveFilter = async () => {
- dispatch("change", tempValue)
+ function addNumbering(filters) {
+ let count = 1
+ for (let value of filters) {
+ if (value.field && value.field?.match(QUERY_START_REGEX) == null) {
+ value.field = `${count++}:${value.field}`
+ }
+ }
+ return filters
+ }
+
+ function correctFilters(filters) {
+ const corrected = []
+ for (let filter of filters) {
+ let field = filter.field
+ if (filter.operator === "allOr") {
+ initialAllOr = allOr = true
+ continue
+ }
+ if (
+ typeof filter.field === "string" &&
+ filter.field.match(QUERY_START_REGEX) != null
+ ) {
+ const parts = field.split(":")
+ const number = parts[0]
+ // it's the new format, remove number
+ if (!isNaN(parseInt(number))) {
+ parts.shift()
+ field = parts.join(":")
+ }
+ }
+ corrected.push({
+ ...filter,
+ field,
+ })
+ }
+ return corrected
+ }
+
+ async function saveFilter() {
+ if (!toSaveFilters && allOr !== initialAllOr) {
+ toSaveFilters = initialFilters
+ }
+ const filters = toSaveFilters?.filter(filter => filter.operator !== "allOr")
+ if (allOr && filters) {
+ filters.push({ operator: "allOr" })
+ }
+ // only save if anything was updated
+ if (filters) {
+ dispatch("change", addNumbering(filters))
+ }
notifications.success("Filters saved.")
drawer.hide()
}
@@ -33,8 +85,12 @@
{
+ toSaveFilters = event.detail
+ }}
/>
diff --git a/packages/frontend-core/src/utils/lucene.js b/packages/frontend-core/src/utils/lucene.js
index b6699628d1..780cb2da39 100644
--- a/packages/frontend-core/src/utils/lucene.js
+++ b/packages/frontend-core/src/utils/lucene.js
@@ -103,6 +103,10 @@ export const buildLuceneQuery = filter => {
const isHbs =
typeof value === "string" && value.match(HBS_REGEX)?.length > 0
// Parse all values into correct types
+ if (operator === "allOr") {
+ query.allOr = true
+ return
+ }
if (type === "datetime") {
// Ensure date value is a valid date and parse into correct format
if (!value) {
diff --git a/packages/server/src/api/controllers/row/internalSearch.js b/packages/server/src/api/controllers/row/internalSearch.js
index 8a04fc2bd0..ab084d9e0b 100644
--- a/packages/server/src/api/controllers/row/internalSearch.js
+++ b/packages/server/src/api/controllers/row/internalSearch.js
@@ -1,4 +1,5 @@
const { SearchIndexes } = require("../../../db/utils")
+const { removeKeyNumbering } = require("./utils")
const fetch = require("node-fetch")
const { getCouchInfo } = require("@budibase/backend-core/db")
const { getAppId } = require("@budibase/backend-core/context")
@@ -197,6 +198,8 @@ class QueryBuilder {
function build(structure, queryFn) {
for (let [key, value] of Object.entries(structure)) {
+ // check for new format - remove numbering if needed
+ key = removeKeyNumbering(key)
key = builder.preprocess(key.replace(/ /g, "_"), {
escape: true,
})
diff --git a/packages/server/src/api/controllers/row/utils.js b/packages/server/src/api/controllers/row/utils.js
index 5da7ca331e..da14020757 100644
--- a/packages/server/src/api/controllers/row/utils.js
+++ b/packages/server/src/api/controllers/row/utils.js
@@ -3,8 +3,11 @@ const { cloneDeep } = require("lodash/fp")
const { InternalTables } = require("../../../db/utils")
const userController = require("../user")
const { FieldTypes } = require("../../../constants")
-const { makeExternalQuery } = require("../../../integrations/base/utils")
const { getAppDB } = require("@budibase/backend-core/context")
+const {
+ makeExternalQuery,
+ removeKeyNumbering,
+} = require("../../../integrations/base/utils")
validateJs.extend(validateJs.validators.datetime, {
parse: function (value) {
@@ -16,6 +19,8 @@ validateJs.extend(validateJs.validators.datetime, {
},
})
+exports.removeKeyNumbering = removeKeyNumbering
+
exports.getDatasourceAndQuery = async json => {
const datasourceId = json.endpoint.datasourceId
const db = getAppDB()
diff --git a/packages/server/src/integrations/base/sql.ts b/packages/server/src/integrations/base/sql.ts
index 750564c6ff..a46ce7aea2 100644
--- a/packages/server/src/integrations/base/sql.ts
+++ b/packages/server/src/integrations/base/sql.ts
@@ -10,6 +10,7 @@ import {
import { isIsoDateString, SqlClients } from "../utils"
import SqlTableQueryBuilder from "./sqlTable"
import environment from "../../environment"
+import { removeKeyNumbering } from "./utils"
const envLimit = environment.SQL_MAX_ROWS
? parseInt(environment.SQL_MAX_ROWS)
@@ -133,12 +134,13 @@ class InternalBuilder {
fn: (key: string, value: any) => void
) {
for (let [key, value] of Object.entries(structure)) {
- const isRelationshipField = key.includes(".")
+ const updatedKey = removeKeyNumbering(key)
+ const isRelationshipField = updatedKey.includes(".")
if (!opts.relationship && !isRelationshipField) {
- fn(`${opts.tableName}.${key}`, value)
+ fn(`${opts.tableName}.${updatedKey}`, value)
}
if (opts.relationship && isRelationshipField) {
- fn(key, value)
+ fn(updatedKey, value)
}
}
}
diff --git a/packages/server/src/integrations/base/utils.ts b/packages/server/src/integrations/base/utils.ts
index 086912b920..0913d59b2a 100644
--- a/packages/server/src/integrations/base/utils.ts
+++ b/packages/server/src/integrations/base/utils.ts
@@ -1,22 +1,30 @@
import { QueryJson } from "../../definitions/datasource"
import { Datasource } from "../../definitions/common"
+const { integrations } = require("../index")
-module DatasourceUtils {
- const { integrations } = require("../index")
+const QUERY_START_REGEX = /\d[0-9]*:/g
- export async function makeExternalQuery(
- datasource: Datasource,
- json: QueryJson
- ) {
- const Integration = integrations[datasource.source]
- // query is the opinionated function
- if (Integration.prototype.query) {
- const integration = new Integration(datasource.config)
- return integration.query(json)
- } else {
- throw "Datasource does not support query."
- }
+export async function makeExternalQuery(
+ datasource: Datasource,
+ json: QueryJson
+) {
+ const Integration = integrations[datasource.source]
+ // query is the opinionated function
+ if (Integration.prototype.query) {
+ const integration = new Integration(datasource.config)
+ return integration.query(json)
+ } else {
+ throw "Datasource does not support query."
+ }
+}
+
+export function removeKeyNumbering(key: any): string {
+ if (typeof key === "string" && key.match(QUERY_START_REGEX) != null) {
+ const parts = key.split(":")
+ // remove the number
+ parts.shift()
+ return parts.join(":")
+ } else {
+ return key
}
-
- module.exports.makeExternalQuery = makeExternalQuery
}
diff --git a/packages/server/src/integrations/queries/sql.ts b/packages/server/src/integrations/queries/sql.ts
index 271a414d44..7fbcfea0a3 100644
--- a/packages/server/src/integrations/queries/sql.ts
+++ b/packages/server/src/integrations/queries/sql.ts
@@ -1,5 +1,4 @@
import { findHBSBlocks, processStringSync } from "@budibase/string-templates"
-import { Integration } from "../../definitions/datasource"
import { DatasourcePlus } from "../base/datasourcePlus"
const CONST_CHAR_REGEX = new RegExp("'[^']*'", "g")
From d79b95b710cecd93230d93bb15dbb6095a8c1462 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Tue, 2 Aug 2022 18:47:00 +0100
Subject: [PATCH 116/320] Update users page to be narrow and remove name column
---
packages/bbui/src/Table/Table.svelte | 13 -------------
.../pages/builder/portal/manage/_layout.svelte | 1 -
.../_components/GroupsTableRenderer.svelte | 17 +++++------------
.../builder/portal/manage/users/index.svelte | 9 ++++-----
4 files changed, 9 insertions(+), 31 deletions(-)
diff --git a/packages/bbui/src/Table/Table.svelte b/packages/bbui/src/Table/Table.svelte
index c929e02d86..01a2ca4835 100644
--- a/packages/bbui/src/Table/Table.svelte
+++ b/packages/bbui/src/Table/Table.svelte
@@ -503,12 +503,6 @@
.spectrum-Table-headCell--alignRight {
justify-content: flex-end;
}
- .spectrum-Table-headCell--divider {
- padding-right: var(--cell-padding);
- }
- .spectrum-Table-headCell--divider + .spectrum-Table-headCell {
- padding-left: var(--cell-padding);
- }
.spectrum-Table-headCell--edit {
position: sticky;
left: 0;
@@ -580,13 +574,6 @@
background-color: var(--table-bg);
z-index: auto;
}
- .spectrum-Table-cell--divider {
- padding-right: var(--cell-padding);
- }
- .spectrum-Table-cell--divider + .spectrum-Table-cell {
- padding-left: var(--cell-padding);
- }
-
.spectrum-Table-cell--edit {
position: sticky;
left: 0;
diff --git a/packages/builder/src/pages/builder/portal/manage/_layout.svelte b/packages/builder/src/pages/builder/portal/manage/_layout.svelte
index a63195a214..47d9aaf59e 100644
--- a/packages/builder/src/pages/builder/portal/manage/_layout.svelte
+++ b/packages/builder/src/pages/builder/portal/manage/_layout.svelte
@@ -12,7 +12,6 @@
$: wide =
$page.path.includes("email/:template") ||
- ($page.path.includes("users") && !$page.path.includes(":userId")) ||
($page.path.includes("groups") && !$page.path.includes(":groupId"))
diff --git a/packages/builder/src/pages/builder/portal/manage/users/_components/GroupsTableRenderer.svelte b/packages/builder/src/pages/builder/portal/manage/users/_components/GroupsTableRenderer.svelte
index 772b5fe7b9..b334575669 100644
--- a/packages/builder/src/pages/builder/portal/manage/users/_components/GroupsTableRenderer.svelte
+++ b/packages/builder/src/pages/builder/portal/manage/users/_components/GroupsTableRenderer.svelte
@@ -1,5 +1,6 @@
@@ -7,17 +8,9 @@
- {#if value?.length === 0}
-
0
- {:else if value?.length === 1}
-
- {value[0]?.name}
-
- {:else}
-
- {parseInt(value?.length) || 0} groups
-
- {/if}
+
+ {value?.length || 0}
+
From c2bad6133d5afaceee0640338c48008f071c21a9 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Tue, 2 Aug 2022 19:43:01 +0100
Subject: [PATCH 120/320] Fix typo
---
.../portal/manage/users/_components/PasswordModal.svelte | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/packages/builder/src/pages/builder/portal/manage/users/_components/PasswordModal.svelte b/packages/builder/src/pages/builder/portal/manage/users/_components/PasswordModal.svelte
index 01dac8c222..02501f2de0 100644
--- a/packages/builder/src/pages/builder/portal/manage/users/_components/PasswordModal.svelte
+++ b/packages/builder/src/pages/builder/portal/manage/users/_components/PasswordModal.svelte
@@ -49,10 +49,10 @@
cancelText="Cancel"
showCloseIcon={false}
>
- All your new users can be accessed through the autogenerated passwords.
- Make not of these passwords or download the csv
+
+ All your new users can be accessed through the autogenerated passwords. Take
+ note of these passwords or download the CSV file.
+
From 1f7784c6c00040a50164b61f5e5d687f3a00c25c Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Tue, 2 Aug 2022 19:49:15 +0100
Subject: [PATCH 121/320] More improvements to user pages
---
.../users/_components/AddUserModal.svelte | 4 ++--
.../users/_components/ImportUsersModal.svelte | 17 ++++++++++-------
.../builder/portal/manage/users/index.svelte | 3 +++
3 files changed, 15 insertions(+), 9 deletions(-)
diff --git a/packages/builder/src/pages/builder/portal/manage/users/_components/AddUserModal.svelte b/packages/builder/src/pages/builder/portal/manage/users/_components/AddUserModal.svelte
index 44ec8085a6..184ff13eaf 100644
--- a/packages/builder/src/pages/builder/portal/manage/users/_components/AddUserModal.svelte
+++ b/packages/builder/src/pages/builder/portal/manage/users/_components/AddUserModal.svelte
@@ -72,8 +72,8 @@
createUsersFromCsv({ userEmails, usersRole, userGroups })}
disabled={!userEmails.length || !validEmails(userEmails) || !usersRole}
>
- Import your users email addresses from a CSV
+ Import your users email addresses from a CSV file