diff --git a/.github/workflows/release-develop.yml b/.github/workflows/release-develop.yml index 9389c5bd66..57bd5a9640 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 14283aff8f..7ec2725a7f 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 9f9092b399..3f9cedba4f 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. + + + +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. + +### [Get started with Budibase](https://budibase.com) + +

-### 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 -``` - - -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). - -
- ---- - -
- ## 🎓 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,32 +121,22 @@ 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 [![Stargazers over time](https://starchart.cc/Budibase/budibase.svg)](https://starchart.cc/Budibase/budibase) 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 0000000000..f3423763a5 Binary files /dev/null and b/docs/budibase-0.2.1.tgz differ diff --git a/docs/index.yaml b/docs/index.yaml index b050330477..8aa6769207 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 fae161a7d5..66b24f4e49 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 01d5a09efa..59363fab5e 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 d5f9ebee28..d9f8384688 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 fa652ed28f..6849d2712c 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 bce532016a..fcb8278ea5 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 563b1b4193..1a2825a2b5 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 774f7ea102..bd9f6543b3 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 + - path: / + pathType: Prefix + backend: + service: + name: proxy-service + port: + number: 10000 -resources: {} +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 2221ce3534..0e8e6f1390 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 fb6306c442..5adf226bc6 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 2f15b2f551..47199cff4b 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 176db9f497..71126a5b13 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 1c3822e51e..9a53fd0169 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 3644280edf..86f2c0ee52 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 dad1cd5c37..96a1bd75aa 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 308a5c7966..99b03bb349 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 0000000000..c0ca27c0a1 --- /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 f179a24729..82e3c45a1f 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 3b78953204..018619e070 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 66125a314a..4bcb9b74c6 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 dc1e40e517..a19646e6fd 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 e69f5ec204..74e82a2da7 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 5b71dd1b2d..c301792839 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 @@