diff --git a/.github/workflows/release-develop.yml b/.github/workflows/release-develop.yml index 9389c5bd6..57bd5a964 100644 --- a/.github/workflows/release-develop.yml +++ b/.github/workflows/release-develop.yml @@ -9,7 +9,6 @@ env: POSTHOG_TOKEN: ${{ secrets.POSTHOG_TOKEN }} INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }} POSTHOG_URL: ${{ secrets.POSTHOG_URL }} - SENTRY_DSN: ${{ secrets.SENTRY_DSN_SELF_HOST }} jobs: release: diff --git a/.github/workflows/release-selfhost.yml b/.github/workflows/release-selfhost.yml index 14283aff8..7ec2725a7 100644 --- a/.github/workflows/release-selfhost.yml +++ b/.github/workflows/release-selfhost.yml @@ -7,7 +7,6 @@ env: POSTHOG_TOKEN: ${{ secrets.POSTHOG_TOKEN }} INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }} POSTHOG_URL: ${{ secrets.POSTHOG_URL }} - SENTRY_DSN: ${{ secrets.SENTRY_DSN_SELF_HOST }} jobs: release: diff --git a/README.md b/README.md index 9f9092b39..3f9cedba4 100644 --- a/README.md +++ b/README.md @@ -8,18 +8,19 @@

- Build, automate and self-host internal tools in minutes + The low code platform you'll enjoy using

- Budibase is an open-source low-code platform, helping developers and IT professionals build, automate, and ship internal tools on their own infrastructure in minutes. + Budibase is an open source low-code platform, and the easiest way to build internal tools that improve productivity.

🤖 🎨 🚀

+

- Budibase design ui + Budibase design ui

@@ -65,68 +66,25 @@ - **Admin paradise.** Budibase is made to scale. With Budibase, you can self-host on your own infrastructure and globally manage users, onboarding, SMTP, apps, groups, theming and more. You can also provide users/groups with an app portal and disseminate user-management to the group manager. -
- ---- - -
+


## 🏁 Get started -Currently there are two ways to get started with Budibase; Digital Ocean, and Docker. -

- -### Get started with Digital Ocean -The easiest and quickest way to get started, is to use Digital Ocean: -1-click Digital Ocean deploy - - - digital ocean badge - -

- -### Get started with Docker -To get started, you must have docker and docker compose installed on your machine. -Once you have Docker installed, the process takes 5 minutes, with these four steps: - -1. Install the Budibase CLI. -``` -$ npm i -g @budibase/cli -``` + +Deploy Budibase self-Hosted in your existing infrastructure, using Docker, Kubernetes, and Digital Ocean. +Or use Budibase Cloud if you don't need to self-host, and would like to get started quickly. -2. Setup Budibase (select where to store Budibase, and the port to run it on) - -``` -budi hosting --init -``` - - -3. Run Budibase - -``` -budi hosting --start -``` - - -4. Create your admin user - -Enter the email and password for the new admin user. - -Done! You are now ready to build powerful internal tools in minutes. For additional information on how to get started and learn Budibase, visit our [docs](https://docs.budibase.com/getting-started). - -
+### [Get started with Budibase](https://budibase.com) ---- -
+

## 🎓 Learning Budibase The Budibase documentation [lives here](https://docs.budibase.com).
----

@@ -134,22 +92,17 @@ The Budibase documentation [lives here](https://docs.budibase.com). If you have a question or would like to talk with other Budibase users and join our community, please hop over to [Github discussions](https://github.com/Budibase/budibase/discussions) - - -

- ---- +


-
## ❗ 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.
---- -
+

+ ## 🙌 Contributing to Budibase @@ -168,21 +121,15 @@ Budibase is a monorepo managed by lerna. Lerna manages the building and publishi - [packages/server](https://github.com/Budibase/budibase/tree/HEAD/packages/server) - The budibase server. This Koa app is responsible for serving the JS for the builder and budibase apps, as well as providing the API for interaction with the database and file system. For more information, see [CONTRIBUTING.md](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md) -

- ----

+ ## 📝 License Budibase is open-source, licensed as [GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html). The client and component libraries are licensed as [MPL](https://directory.fsf.org/wiki/License:MPL-2.0) - so the apps that you build can be licensed however you like. -

- ---- - -
+

## ⭐ Stargazers over time @@ -190,10 +137,6 @@ Budibase is open-source, licensed as [GPL v3](https://www.gnu.org/licenses/gpl-3 If you are having issues between updates of the builder, please use the guide [here](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md#troubleshooting) to clear down your environment. -
- ---- -

## Contributors ✨ diff --git a/docs/budibase-0.2.1.tgz b/docs/budibase-0.2.1.tgz new file mode 100644 index 000000000..f3423763a Binary files /dev/null and b/docs/budibase-0.2.1.tgz differ diff --git a/docs/index.yaml b/docs/index.yaml index b05033047..8aa676920 100644 --- a/docs/index.yaml +++ b/docs/index.yaml @@ -3,7 +3,7 @@ entries: budibase: - apiVersion: v2 appVersion: 0.9.163 - created: "2021-10-12T21:58:00.515555+01:00" + created: "2021-10-18T16:25:04.374924613+01:00" dependencies: - condition: services.couchdb.enabled name: couchdb @@ -13,7 +13,35 @@ entries: name: ingress-nginx repository: https://github.com/kubernetes/ingress-nginx version: 3.35.0 - description: Budibase is an open source low-code platform, helping thousands of teams build apps for their workplace in minutes. + description: Budibase is an open source low-code platform, helping thousands of + teams build apps for their workplace in minutes. + digest: ebac6d8631cc38b266c3689508b5123f5afc395f23bdb02738be26c7cae0b0b5 + keywords: + - low-code + - database + - cluster + name: budibase + sources: + - https://github.com/Budibase/budibase + - https://budibase.com + type: application + urls: + - https://budibase.github.io/budibase/budibase-0.2.1.tgz + version: 0.2.1 + - apiVersion: v2 + appVersion: 0.9.163 + created: "2021-10-18T16:25:04.36936805+01:00" + dependencies: + - condition: services.couchdb.enabled + name: couchdb + repository: https://apache.github.io/couchdb-helm + version: 3.3.4 + - condition: ingress.nginx + name: ingress-nginx + repository: https://github.com/kubernetes/ingress-nginx + version: 3.35.0 + description: Budibase is an open source low-code platform, helping thousands of + teams build apps for their workplace in minutes. digest: f369536c0eac1f6959d51e8ce6d74a87a7a9df29ae84fb9cbed0a273ab77429b keywords: - low-code @@ -29,7 +57,7 @@ entries: version: 0.2.0 - apiVersion: v2 appVersion: 0.9.56 - created: "2021-10-12T21:58:00.512062+01:00" + created: "2021-10-18T16:25:04.36360616+01:00" dependencies: - condition: services.couchdb.enabled name: couchdb @@ -38,7 +66,8 @@ entries: - name: ingress-nginx repository: https://github.com/kubernetes/ingress-nginx version: 3.35.0 - description: Budibase is an open source low-code platform, helping thousands of teams build apps for their workplace in minutes. + description: Budibase is an open source low-code platform, helping thousands of + teams build apps for their workplace in minutes. digest: 8dc4f2ed4d98cad5adf25936aefea680042d3e4e17832f846b961fd8708ad192 keywords: - low-code @@ -54,7 +83,7 @@ entries: version: 0.1.1 - apiVersion: v2 appVersion: 0.9.56 - created: "2021-10-12T21:58:00.507257+01:00" + created: "2021-10-18T16:25:04.354504201+01:00" dependencies: - condition: services.couchdb.enabled name: couchdb @@ -63,7 +92,8 @@ entries: - name: ingress-nginx repository: https://github.com/kubernetes/ingress-nginx version: 3.35.0 - description: Budibase is an open source low-code platform, helping thousands of teams build apps for their workplace in minutes. + description: Budibase is an open source low-code platform, helping thousands of + teams build apps for their workplace in minutes. digest: 08031b0803cce0eff64472e569d454d9176119c8207aa9873a9c95ee66cc7d3f keywords: - low-code @@ -77,4 +107,4 @@ entries: urls: - https://budibase.github.io/budibase/budibase-0.1.0.tgz version: 0.1.0 -generated: "2021-10-12T21:58:00.503447+01:00" +generated: "2021-10-18T16:25:04.346266269+01:00" diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index fae161a7d..66b24f4e4 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -21,7 +21,7 @@ services: PORT: 4002 JWT_SECRET: ${JWT_SECRET} LOG_LEVEL: info - SENTRY_DSN: https://cc54bb0358fd4300ae97ef2273fbaf9f@o420233.ingest.sentry.io/6007553 + SENTRY_DSN: https://a34ae347621946bf8acded18e5b7d4b8@o420233.ingest.sentry.io/5338131 ENABLE_ANALYTICS: "true" REDIS_URL: redis-service:6379 REDIS_PASSWORD: ${REDIS_PASSWORD} @@ -48,10 +48,10 @@ services: COUCH_DB_USERNAME: ${COUCH_DB_USER} COUCH_DB_PASSWORD: ${COUCH_DB_PASSWORD} COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984 + SENTRY_DSN: https://a34ae347621946bf8acded18e5b7d4b8@o420233.ingest.sentry.io/5338131 INTERNAL_API_KEY: ${INTERNAL_API_KEY} REDIS_URL: redis-service:6379 REDIS_PASSWORD: ${REDIS_PASSWORD} - ACCOUNT_PORTAL_URL: https://portal.budi.live volumes: - ./logs:/logs depends_on: diff --git a/hosting/envoy.dev.yaml.hbs b/hosting/envoy.dev.yaml.hbs index 01d5a09ef..59363fab5 100644 --- a/hosting/envoy.dev.yaml.hbs +++ b/hosting/envoy.dev.yaml.hbs @@ -41,6 +41,7 @@ static_resources: - match: { prefix: "/api/" } route: cluster: server-dev + timeout: 120s - match: { prefix: "/app_" } route: diff --git a/hosting/envoy.yaml b/hosting/envoy.yaml index d5f9ebee2..d9f838468 100644 --- a/hosting/envoy.yaml +++ b/hosting/envoy.yaml @@ -58,6 +58,7 @@ static_resources: - match: { prefix: "/api/" } route: cluster: app-service + timeout: 120s - match: { prefix: "/worker/" } route: diff --git a/hosting/kubernetes/budibase/Chart.yaml b/hosting/kubernetes/budibase/Chart.yaml index fa652ed28..6849d2712 100644 --- a/hosting/kubernetes/budibase/Chart.yaml +++ b/hosting/kubernetes/budibase/Chart.yaml @@ -22,7 +22,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.2.0 +version: 0.2.1 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/hosting/kubernetes/budibase/templates/app-service-deployment.yaml b/hosting/kubernetes/budibase/templates/app-service-deployment.yaml index bce532016..fcb8278ea 100644 --- a/hosting/kubernetes/budibase/templates/app-service-deployment.yaml +++ b/hosting/kubernetes/budibase/templates/app-service-deployment.yaml @@ -73,11 +73,15 @@ spec: name: {{ template "budibase.fullname" . }} key: objectStoreSecret - name: MINIO_URL + {{ if .Values.services.objectStore.url }} value: {{ .Values.services.objectStore.url }} + {{ else }} + value: http://minio-service:{{ .Values.services.objectStore.port }} + {{ end }} - name: PORT value: {{ .Values.services.apps.port | quote }} - name: MULTI_TENANCY - value: "1" + value: {{ .Values.globals.multiTenancy | quote }} - name: REDIS_PASSWORD value: {{ .Values.services.redis.password }} - name: REDIS_URL diff --git a/hosting/kubernetes/budibase/templates/worker-service-deployment.yaml b/hosting/kubernetes/budibase/templates/worker-service-deployment.yaml index 563b1b419..1a2825a2b 100644 --- a/hosting/kubernetes/budibase/templates/worker-service-deployment.yaml +++ b/hosting/kubernetes/budibase/templates/worker-service-deployment.yaml @@ -70,11 +70,15 @@ spec: name: {{ template "budibase.fullname" . }} key: objectStoreSecret - name: MINIO_URL + {{ if .Values.services.objectStore.url }} value: {{ .Values.services.objectStore.url }} + {{ else }} + value: http://minio-service:{{ .Values.services.objectStore.port }} + {{ end }} - name: PORT value: {{ .Values.services.worker.port | quote }} - name: MULTI_TENANCY - value: "1" + value: {{ .Values.globals.multiTenancy | quote }} - name: REDIS_PASSWORD value: {{ .Values.services.redis.password | quote }} - name: REDIS_URL @@ -85,6 +89,8 @@ spec: {{ end }} - name: SELF_HOSTED value: {{ .Values.globals.selfHosted | quote }} + - name: SENTRY_DSN + value: {{ .Values.globals.sentryDSN }} - name: ACCOUNT_PORTAL_URL value: {{ .Values.globals.accountPortalUrl | quote }} - name: ACCOUNT_PORTAL_API_KEY diff --git a/hosting/kubernetes/budibase/values.yaml b/hosting/kubernetes/budibase/values.yaml index 774f7ea10..bd9f6543b 100644 --- a/hosting/kubernetes/budibase/values.yaml +++ b/hosting/kubernetes/budibase/values.yaml @@ -24,10 +24,12 @@ serviceAccount: podAnnotations: {} -podSecurityContext: {} +podSecurityContext: + {} # fsGroup: 2000 -securityContext: {} +securityContext: + {} # capabilities: # drop: # - ALL @@ -42,23 +44,24 @@ service: ingress: enabled: false aws: false - nginx: true - certificateArn: "" + nginx: true + certificateArn: "" className: "" - annotations: + annotations: kubernetes.io/ingress.class: nginx hosts: - host: # change if using custom domain paths: - - path: / - pathType: Prefix - backend: - service: - name: proxy-service - port: - number: 10000 - -resources: {} + - path: / + pathType: Prefix + backend: + service: + name: proxy-service + port: + number: 10000 + +resources: + {} # We usually recommend not to specify default resources and to leave this as a conscious # choice for the user. This also increases chances charts run on environments with little # resources, such as Minikube. If you do want to specify resources, uncomment the following @@ -89,10 +92,11 @@ globals: sentryDSN: "" posthogToken: "" logLevel: info - selfHosted: "" - accountPortalUrl: "" + selfHosted: "1" # set to 0 for budibase cloud environment, set to 1 for self-hosted setup + multiTenancy: "0" # set to 0 to disable multiple orgs, set to 1 to enable multiple orgs + accountPortalUrl: "" accountPortalApiKey: "" - cookieDomain: "" + cookieDomain: "" platformUrl: "" createSecrets: true # creates an internal API key, JWT secrets and redis password for you @@ -128,7 +132,7 @@ services: # password: "" # only change if pointing to existing couch server port: 5984 storage: 100Mi - + redis: enabled: true # disable if using external redis port: 6379 @@ -136,7 +140,7 @@ services: url: "" # only change if pointing to existing redis cluster and enabled: false password: "budibase" # recommended to override if using built-in redis storage: 100Mi - + objectStore: minio: true browser: true @@ -147,4 +151,3 @@ services: region: "" # AWS_REGION if using S3 or existing minio secret url: "" # only change if pointing to existing minio cluster and minio: false storage: 100Mi - diff --git a/lerna.json b/lerna.json index 2221ce353..0e8e6f139 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.9.167-alpha.2", + "version": "0.9.169-alpha.10", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/auth/package.json b/packages/auth/package.json index fb6306c44..5adf226bc 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/auth", - "version": "0.9.167-alpha.2", + "version": "0.9.169-alpha.10", "description": "Authentication middlewares for budibase builder and apps", "main": "src/index.js", "author": "Budibase", diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 2f15b2f55..47199cff4 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "0.9.167-alpha.2", + "version": "0.9.169-alpha.10", "license": "AGPL-3.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", diff --git a/packages/bbui/src/Form/Core/DatePicker.svelte b/packages/bbui/src/Form/Core/DatePicker.svelte index 176db9f49..71126a5b1 100644 --- a/packages/bbui/src/Form/Core/DatePicker.svelte +++ b/packages/bbui/src/Form/Core/DatePicker.svelte @@ -31,7 +31,11 @@ const handleChange = event => { const [dates] = event.detail - dispatch("change", dates[0]) + let newValue = dates[0] + if (newValue) { + newValue = newValue.toISOString() + } + dispatch("change", newValue) } const clearDateOnBackspace = event => { @@ -57,11 +61,38 @@ const els = document.querySelectorAll(`#${flatpickrId} input`) els.forEach(el => el.blur()) } + + const parseDate = val => { + if (!val) { + return null + } + let date + if (val instanceof Date) { + // Use real date obj if already parsed + date = val + } else if (isNaN(val)) { + // Treat as date string of some sort + date = new Date(val) + } else { + // Treat as numerical timestamp + date = new Date(parseInt(val)) + } + const time = date.getTime() + if (isNaN(time)) { + return null + } + // By rounding to the nearest second we avoid locking up in an endless + // loop in the builder, caused by potentially enriching {{ now }} to every + // millisecond. + return new Date(Math.floor(time / 1000) * 1000) + } + + $: console.log(value) { - const isoString = e.detail.toISOString() - value = isoString - dispatch("change", isoString) + value = e.detail + dispatch("change", e.detail) } diff --git a/packages/bbui/src/Table/CellRenderer.svelte b/packages/bbui/src/Table/CellRenderer.svelte index 1c3822e51..9a53fd016 100644 --- a/packages/bbui/src/Table/CellRenderer.svelte +++ b/packages/bbui/src/Table/CellRenderer.svelte @@ -25,7 +25,6 @@ array: ArrayRenderer, internal: InternalRenderer, } - $: type = schema?.type ?? "string" $: customRenderer = customRenderers?.find(x => x.column === schema?.name) $: renderer = customRenderer?.component ?? typeMap[type] ?? StringRenderer diff --git a/packages/bbui/src/Tabs/Tab.svelte b/packages/bbui/src/Tabs/Tab.svelte index 3644280ed..86f2c0ee5 100644 --- a/packages/bbui/src/Tabs/Tab.svelte +++ b/packages/bbui/src/Tabs/Tab.svelte @@ -8,11 +8,19 @@ const selected = getContext("tab") let tab let tabInfo + const setTabInfo = () => { - tabInfo = tab.getBoundingClientRect() - if ($selected.title === title) { - $selected.info = tabInfo - } + // If the tabs are being rendered inside a component which uses + // a svelte transition to enter, then this initial getBoundingClientRect + // will return an incorrect position. + // We just need to get this off the main thread to fix this, by using + // a 0ms timeout. + setTimeout(() => { + tabInfo = tab.getBoundingClientRect() + if ($selected.title === title) { + $selected.info = tabInfo + } + }, 0) } onMount(() => { diff --git a/packages/builder/cypress/integration/createTable.spec.js b/packages/builder/cypress/integration/createTable.spec.js index dad1cd5c3..96a1bd75a 100644 --- a/packages/builder/cypress/integration/createTable.spec.js +++ b/packages/builder/cypress/integration/createTable.spec.js @@ -31,7 +31,7 @@ context("Create a Table", () => { cy.contains("nameupdated ").should("contain", "nameupdated") }) - /* + it("edits a row", () => { cy.contains("button", "Edit").click({ force: true }) cy.wait(1000) @@ -40,7 +40,7 @@ context("Create a Table", () => { cy.contains("Save").click() cy.contains("Updated").should("have.text", "Updated") }) - */ + it("deletes a row", () => { cy.get(".spectrum-Checkbox-input").check({ force: true }) cy.contains("Delete 1 row(s)").click() diff --git a/packages/builder/cypress/integration/customThemingProperties.spec.js b/packages/builder/cypress/integration/customThemingProperties.spec.js index 308a5c796..99b03bb34 100644 --- a/packages/builder/cypress/integration/customThemingProperties.spec.js +++ b/packages/builder/cypress/integration/customThemingProperties.spec.js @@ -5,12 +5,12 @@ context("Custom Theming Properties", () => { cy.navigateToFrontend() }) - // Default Values - // Button roundness = Large - // Accent colour = Blue 600 - // Accent colour (hover) = Blue 500 - // Navigation bar background colour = Gray 100 - // Navigation bar text colour = Gray 800 + /* Default Values: + Button roundness = Large + Accent colour = Blue 600 + Accent colour (hover) = Blue 500 + Navigation bar background colour = Gray 100 + Navigation bar text colour = Gray 800 */ it("should reset the color property values", () => { // Open Theme modal and change colours cy.get(".spectrum-ActionButton-label").contains("Theme").click() @@ -24,6 +24,29 @@ context("Custom Theming Properties", () => { checkThemeColorDefaults() }) + /* Button Roundness Values: + None = 0 + Small = 4px + Medium = 8px + Large = 16px */ + it.only("should test button roundness", () => { + const buttonRoundnessValues = ["0", "4px", "8px", "16px"] + cy.wait(1000) + // Add button, change roundness and confirm value + cy.addComponent("Button", null).then((componentId) => { + buttonRoundnessValues.forEach(function (item, index){ + cy.get(".spectrum-ActionButton-label").contains("Theme").click() + cy.get(".setting").contains("Button roundness").parent() + .get(".select-wrapper").click() + cy.get(".spectrum-Popover").find('li').eq(index).click() + cy.get(".spectrum-Button").contains("View changes").click({force: true}) + cy.reload() + cy.getComponent(componentId) + .parents(".svelte-xiqd1c").eq(0).should('have.attr', 'style').and('contains', `--buttonBorderRadius:${item}`) + }) + }) + }) + const changeThemeColors = () => { // Changes the theme colours cy.get(".spectrum-FieldLabel").contains("Accent color") diff --git a/packages/builder/cypress/integration/renameAnApplication.spec.js b/packages/builder/cypress/integration/renameAnApplication.spec.js new file mode 100644 index 000000000..c0ca27c0a --- /dev/null +++ b/packages/builder/cypress/integration/renameAnApplication.spec.js @@ -0,0 +1,102 @@ +context("Rename an App", () => { + beforeEach(() => { + cy.login() + cy.createTestApp() + }) + +it("should rename an unpublished application", () => { + const appRename = "Cypress Renamed" + // Rename app, Search for app, Confirm name was changed + cy.get(".home-logo").click() + renameApp(appRename) + cy.searchForApplication(appRename) + cy.get(".appGrid").find(".wrapper").should("have.length", 1) + }) + +it("Should rename a published application", () => { + // It is not possible to rename a published application + const appRename = "Cypress Renamed" + // Publish the app + cy.get(".toprightnav") + cy.get(".spectrum-Button").contains("Publish").click({force: true}) + cy.get(".spectrum-Dialog-grid") + .within(() => { + // Click publish again within the modal + cy.get(".spectrum-Button").contains("Publish").click({force: true}) + }) + // Rename app, Search for app, Confirm name was changed + cy.get(".home-logo").click() + renameApp(appRename, true) + cy.searchForApplication(appRename) + cy.get(".appGrid").find(".wrapper").should("have.length", 1) +}) + +it("Should try to rename an application to have no name", () => { + cy.get(".home-logo").click() + renameApp(" ", false, true) + // Close modal and confirm name has not been changed + cy.get(".spectrum-Dialog-grid").contains("Cancel").click() + cy.searchForApplication("Cypress Tests") + cy.get(".appGrid").find(".wrapper").should("have.length", 1) +}) + +it("Should create two applications with the same name", () => { + // It is not possible to have applications with the same name + const appName = "Cypress Tests" + cy.visit(`localhost:${Cypress.env("PORT")}/builder`) + cy.wait(500) + cy.get(".spectrum-Button").contains("Create app").click({force: true}) + cy.contains(/Start from scratch/).click() + cy.get(".spectrum-Modal") + .within(() => { + cy.get("input").eq(0).type(appName) + cy.get(".spectrum-ButtonGroup").contains("Create app").click({force: true}) + cy.get(".error").should("have.text", "Another app with the same name already exists") + }) +}) + +it("should validate application names", () => { + // App name must be letters, numbers and spaces only + // This test checks numbers and special characters specifically + const numberName = 12345 + const specialCharName = "£$%^" + cy.get(".home-logo").click() + renameApp(numberName) + cy.searchForApplication(numberName) + cy.get(".appGrid").find(".wrapper").should("have.length", 1) + renameApp(specialCharName) + cy.get(".error").should("have.text", "App name must be letters, numbers and spaces only") +}) + + const renameApp = (appName, published, noName) => { + cy.request(`localhost:${Cypress.env("PORT")}/api/applications?status=all`) + .its("body") + .then(val => { + if (val.length > 0) { + cy.get(".title > :nth-child(3) > .spectrum-Icon").click() + // Check for when an app is published + if (published == true){ + // Should not have Edit as option, will unpublish app + cy.should("not.have.value", "Edit") + cy.get(".spectrum-Menu").contains("Unpublish").click() + cy.get(".spectrum-Dialog-grid").contains("Unpublish app").click() + cy.get(".title > :nth-child(3) > .spectrum-Icon").click() + } + cy.contains("Edit").click() + cy.get(".spectrum-Modal") + .within(() => { + if (noName == true){ + cy.get("input").clear() + cy.get(".spectrum-Dialog-grid").click() + .contains("App name must be letters, numbers and spaces only") + return cy + } + cy.get("input").clear() + cy.get("input").eq(0).type(appName).should("have.value", appName).blur() + cy.get(".spectrum-ButtonGroup").contains("Save").click({force: true}) + cy.wait(500) + }) + } + }) +} +}) diff --git a/packages/builder/cypress/support/commands.js b/packages/builder/cypress/support/commands.js index f179a2472..82e3c45a1 100644 --- a/packages/builder/cypress/support/commands.js +++ b/packages/builder/cypress/support/commands.js @@ -35,19 +35,12 @@ Cypress.Commands.add("login", () => { Cypress.Commands.add("createApp", name => { cy.visit(`localhost:${Cypress.env("PORT")}/builder`) cy.wait(500) - cy.contains(/Start from scratch/).click() - cy.get(".spectrum-Modal") - .within(() => { - cy.get("input").eq(0).type(name).should("have.value", name).blur() - cy.get(".spectrum-ButtonGroup").contains("Create app").click() - cy.wait(7000) - }) - .then(() => { - // Because we show the datasource modal on entry, we need to create a table to get rid of the modal in the future - cy.createInitialDatasource("initialTable") - cy.expandBudibaseConnection() - cy.get(".nav-item.selected > .content").should("be.visible") - }) + cy.contains(/Start from scratch/).dblclick() + cy.get(".spectrum-Modal").within(() => { + cy.get("input").eq(0).type(name).should("have.value", name).blur() + cy.get(".spectrum-ButtonGroup").contains("Create app").click() + cy.wait(7000) + }) }) Cypress.Commands.add("deleteApp", () => { @@ -77,22 +70,6 @@ Cypress.Commands.add("createTestTableWithData", () => { cy.addColumn("dog", "age", "Number") }) -Cypress.Commands.add("createInitialDatasource", tableName => { - // Enter table name - cy.get(".spectrum-Modal").within(() => { - cy.contains("Budibase DB").trigger("mouseover").click().click() - cy.wait(1000) - cy.contains("Continue").click() - }) - - cy.get(".spectrum-Modal").within(() => { - cy.wait(1000) - cy.get("input").first().type(tableName).blur() - cy.get(".spectrum-ButtonGroup").contains("Create").click() - }) - cy.contains(tableName).should("be.visible") -}) - Cypress.Commands.add("createTable", tableName => { cy.contains("Budibase DB").click() cy.contains("Create new table").click() @@ -247,3 +224,9 @@ Cypress.Commands.add("addCustomSourceOptions", totalOptions => { cy.get(".spectrum-Button").contains("Save").click({ force: true }) }) }) + +Cypress.Commands.add("searchForApplication", appName => { + cy.get(".spectrum-Textfield").within(() => { + cy.get("input").eq(0).type(appName) + }) +}) diff --git a/packages/builder/package.json b/packages/builder/package.json index 3b7895320..018619e07 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.9.167-alpha.2", + "version": "0.9.169-alpha.10", "license": "AGPL-3.0", "private": true, "scripts": { @@ -65,11 +65,11 @@ } }, "dependencies": { - "@budibase/bbui": "^0.9.167-alpha.2", - "@budibase/client": "^0.9.167-alpha.2", + "@budibase/bbui": "^0.9.169-alpha.10", + "@budibase/client": "^0.9.169-alpha.10", "@budibase/colorpicker": "1.1.2", - "@budibase/string-templates": "^0.9.167-alpha.2", - "@sentry/browser": "5.19.1", + "@budibase/string-templates": "^0.9.169-alpha.10", + "@sentry/browser": "6.0.0", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", "codemirror": "^5.59.0", diff --git a/packages/builder/src/builderStore/api.js b/packages/builder/src/builderStore/api.js index 66125a314..4bcb9b74c 100644 --- a/packages/builder/src/builderStore/api.js +++ b/packages/builder/src/builderStore/api.js @@ -15,7 +15,7 @@ const apiCall = if (resp.status === 403) { removeCookie(Cookies.Auth) // reload after removing cookie, go to login - if (!url.includes("self")) { + if (!url.includes("self") && !url.includes("login")) { location.reload() } } diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index dc1e40e51..a19646e6f 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -7,11 +7,17 @@ import { } from "./storeUtils" import { store } from "builderStore" import { queries as queriesStores, tables as tablesStore } from "stores/backend" -import { makePropSafe } from "@budibase/string-templates" +import { + makePropSafe, + isJSBinding, + decodeJSBinding, + encodeJSBinding, +} from "@budibase/string-templates" import { TableNames } from "../constants" // Regex to match all instances of template strings const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g +const CAPTURE_VAR_INSIDE_JS = /\$\("([^")]+)"\)/g const CAPTURE_HBS_TEMPLATE = /{{[\S\s]*?}}/g /** @@ -430,6 +436,15 @@ function replaceBetween(string, start, end, replacement) { * utility function for the readableToRuntimeBinding and runtimeToReadableBinding. */ function bindingReplacement(bindableProperties, textWithBindings, convertTo) { + // Decide from base64 if using JS + const isJS = isJSBinding(textWithBindings) + if (isJS) { + textWithBindings = decodeJSBinding(textWithBindings) + } + + // Determine correct regex to find bindings to replace + const regex = isJS ? CAPTURE_VAR_INSIDE_JS : CAPTURE_VAR_INSIDE_TEMPLATE + const convertFrom = convertTo === "runtimeBinding" ? "readableBinding" : "runtimeBinding" if (typeof textWithBindings !== "string") { @@ -441,7 +456,7 @@ function bindingReplacement(bindableProperties, textWithBindings, convertTo) { .sort((a, b) => { return b.length - a.length }) - const boundValues = textWithBindings.match(CAPTURE_VAR_INSIDE_TEMPLATE) || [] + const boundValues = textWithBindings.match(regex) || [] let result = textWithBindings for (let boundValue of boundValues) { let newBoundValue = boundValue @@ -449,7 +464,7 @@ function bindingReplacement(bindableProperties, textWithBindings, convertTo) { // in the search, working from longest to shortest so always use best match first let searchString = newBoundValue for (let from of convertFromProps) { - if (shouldReplaceBinding(newBoundValue, from, convertTo)) { + if (isJS || shouldReplaceBinding(newBoundValue, from, convertTo)) { const binding = bindableProperties.find(el => el[convertFrom] === from) let idx do { @@ -457,7 +472,7 @@ function bindingReplacement(bindableProperties, textWithBindings, convertTo) { idx = searchString.indexOf(from) if (idx !== -1) { let end = idx + from.length, - searchReplace = Array(binding[convertTo].length).join("*") + searchReplace = Array(binding[convertTo].length + 1).join("*") // blank out parts of the search string searchString = replaceBetween(searchString, idx, end, searchReplace) newBoundValue = replaceBetween( @@ -472,6 +487,12 @@ function bindingReplacement(bindableProperties, textWithBindings, convertTo) { } result = result.replace(boundValue, newBoundValue) } + + // Re-encode to base64 if using JS + if (isJS) { + result = encodeJSBinding(result) + } + return result } diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte index e69f5ec20..74e82a2da 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte @@ -103,7 +103,7 @@ {block?.name?.toUpperCase() || ""} - {#if testResult} + {#if testResult && testResult[0]} resultsModal.show()}> onChange(e, key)} {bindings} + allowJS={false} /> {/if} {:else if value.customType === "query"} @@ -259,6 +260,7 @@ value={inputData[key]} on:change={e => onChange(e, key)} {bindings} + allowJS={false} /> {/if} diff --git a/packages/builder/src/components/automation/SetupPanel/QueryParamSelector.svelte b/packages/builder/src/components/automation/SetupPanel/QueryParamSelector.svelte index 5b71dd1b2..c30179283 100644 --- a/packages/builder/src/components/automation/SetupPanel/QueryParamSelector.svelte +++ b/packages/builder/src/components/automation/SetupPanel/QueryParamSelector.svelte @@ -4,12 +4,23 @@ import { Select } from "@budibase/bbui" import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte" import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte" + import { createEventDispatcher } from "svelte" const dispatch = createEventDispatcher() export let value export let bindings + const onChangeQuery = e => { + value.queryId = e.detail + dispatch("change", value) + } + + const onChange = (e, field) => { + value[field.name] = e.detail + dispatch("change", value) + } + $: query = $queries.list.find(query => query._id === value?.queryId) $: parameters = query?.parameters ?? [] @@ -17,11 +28,8 @@