diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index f004e0acc5..6ace2303d9 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -6,7 +6,7 @@ labels: bug assignees: '' --- -## Checklist +**Checklist** - [ ] I have searched budibase discussions and github issues to check if my issue already exists **Hosting** diff --git a/lerna.json b/lerna.json index 4e6affc904..9332b26f66 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.2.12-alpha.21", + "version": "2.2.12-alpha.41", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 54d31fe088..df2e19ea34 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/backend-core", - "version": "2.2.12-alpha.21", + "version": "2.2.12-alpha.41", "description": "Budibase backend core libraries used in server and worker", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", @@ -23,7 +23,7 @@ }, "dependencies": { "@budibase/nano": "10.1.1", - "@budibase/types": "2.2.12-alpha.21", + "@budibase/types": "2.2.12-alpha.41", "@shopify/jest-koa-mocks": "5.0.1", "@techpass/passport-openidconnect": "0.3.2", "aws-cloudfront-sign": "2.2.0", diff --git a/packages/bbui/package.json b/packages/bbui/package.json index dd92a9101e..6437628002 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": "2.2.12-alpha.21", + "version": "2.2.12-alpha.41", "license": "MPL-2.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", @@ -38,7 +38,8 @@ ], "dependencies": { "@adobe/spectrum-css-workflow-icons": "1.2.1", - "@budibase/string-templates": "2.2.12-alpha.21", + "@budibase/string-templates": "2.2.12-alpha.41", + "@spectrum-css/accordion": "3.0.24", "@spectrum-css/actionbutton": "1.0.1", "@spectrum-css/actiongroup": "1.0.1", "@spectrum-css/avatar": "3.0.2", diff --git a/packages/bbui/src/Accordion/Accordion.svelte b/packages/bbui/src/Accordion/Accordion.svelte new file mode 100644 index 0000000000..1c88450c9a --- /dev/null +++ b/packages/bbui/src/Accordion/Accordion.svelte @@ -0,0 +1,58 @@ + + +
+
+

+ + +

+
+ +
+
+
+ + diff --git a/packages/bbui/src/ActionButton/ActionButton.svelte b/packages/bbui/src/ActionButton/ActionButton.svelte index cfc810807e..cc4417be2a 100644 --- a/packages/bbui/src/ActionButton/ActionButton.svelte +++ b/packages/bbui/src/ActionButton/ActionButton.svelte @@ -88,6 +88,7 @@ } .is-selected:not(.spectrum-ActionButton--emphasized) { background: var(--spectrum-global-color-gray-300); + border-color: var(--spectrum-global-color-gray-700); } .noPadding { padding: 0; diff --git a/packages/bbui/src/FancyForm/FancyButton.svelte b/packages/bbui/src/FancyForm/FancyButton.svelte new file mode 100644 index 0000000000..09615df8fa --- /dev/null +++ b/packages/bbui/src/FancyForm/FancyButton.svelte @@ -0,0 +1,29 @@ + + + + {#if icon} + {#if icon.includes("/")} + button + {:else} + + {/if} + {/if} +
+ +
+
+ + diff --git a/packages/bbui/src/FancyForm/FancyButtonRadio.svelte b/packages/bbui/src/FancyForm/FancyButtonRadio.svelte new file mode 100644 index 0000000000..510fd8efb8 --- /dev/null +++ b/packages/bbui/src/FancyForm/FancyButtonRadio.svelte @@ -0,0 +1,70 @@ + + + + {#if label} + {label} + {/if} + +
+ {#each options as option} + onChange(getOptionValue(option))} + > + {getOptionLabel(option)} + + {/each} +
+
+ + diff --git a/packages/bbui/src/FancyForm/FancyCheckbox.svelte b/packages/bbui/src/FancyForm/FancyCheckbox.svelte new file mode 100644 index 0000000000..191cc79485 --- /dev/null +++ b/packages/bbui/src/FancyForm/FancyCheckbox.svelte @@ -0,0 +1,53 @@ + + + + + + +
+ {#if text} + {text} + {/if} + +
+
+ + diff --git a/packages/bbui/src/FancyForm/FancyField.svelte b/packages/bbui/src/FancyForm/FancyField.svelte new file mode 100644 index 0000000000..89f2dec7d0 --- /dev/null +++ b/packages/bbui/src/FancyForm/FancyField.svelte @@ -0,0 +1,126 @@ + + +
+
+
+ +
+ {#if error} +
+ +
+ {/if} +
+ {#if error} +
+ {error} +
+ {/if} +
+ + diff --git a/packages/bbui/src/FancyForm/FancyFieldLabel.svelte b/packages/bbui/src/FancyForm/FancyFieldLabel.svelte new file mode 100644 index 0000000000..181cff50e4 --- /dev/null +++ b/packages/bbui/src/FancyForm/FancyFieldLabel.svelte @@ -0,0 +1,25 @@ + + +
+ +
+ + diff --git a/packages/bbui/src/FancyForm/FancyForm.svelte b/packages/bbui/src/FancyForm/FancyForm.svelte new file mode 100644 index 0000000000..f874238572 --- /dev/null +++ b/packages/bbui/src/FancyForm/FancyForm.svelte @@ -0,0 +1,40 @@ + + +
+ +
+ + diff --git a/packages/bbui/src/FancyForm/FancyInput.svelte b/packages/bbui/src/FancyForm/FancyInput.svelte new file mode 100644 index 0000000000..8735e2c30c --- /dev/null +++ b/packages/bbui/src/FancyForm/FancyInput.svelte @@ -0,0 +1,77 @@ + + + + {#if label} + {label} + {/if} + (focused = true)} + on:blur={() => (focused = false)} + class:placeholder + /> + {#if suffix && !placeholder} +
{suffix}
+ {/if} +
+ + diff --git a/packages/bbui/src/FancyForm/FancySelect.svelte b/packages/bbui/src/FancyForm/FancySelect.svelte new file mode 100644 index 0000000000..ee43ecc3ca --- /dev/null +++ b/packages/bbui/src/FancyForm/FancySelect.svelte @@ -0,0 +1,147 @@ + + + (open = true)} +> + {#if label} + {label} + {/if} + +
+ {selectedLabel || ""} +
+ +
+ +
+
+ + (open = false)} + useAnchorWidth={true} + maxWidth={null} +> +
+ {#if options.length} + {#each options as option, idx} +
onChange(getOptionValue(option, idx))} + > + + {getOptionLabel(option, idx)} + + {#if value === getOptionValue(option, idx)} + + {/if} +
+ {/each} + {/if} +
+
+ + diff --git a/packages/bbui/src/FancyForm/index.js b/packages/bbui/src/FancyForm/index.js new file mode 100644 index 0000000000..241036fb35 --- /dev/null +++ b/packages/bbui/src/FancyForm/index.js @@ -0,0 +1,6 @@ +export { default as FancyInput } from "./FancyInput.svelte" +export { default as FancyCheckbox } from "./FancyCheckbox.svelte" +export { default as FancySelect } from "./FancySelect.svelte" +export { default as FancyButton } from "./FancyButton.svelte" +export { default as FancyForm } from "./FancyForm.svelte" +export { default as FancyButtonRadio } from "./FancyButtonRadio.svelte" diff --git a/packages/bbui/src/Form/Core/Picker.svelte b/packages/bbui/src/Form/Core/Picker.svelte index 81348452c7..32cfcf3310 100644 --- a/packages/bbui/src/Form/Core/Picker.svelte +++ b/packages/bbui/src/Form/Core/Picker.svelte @@ -45,7 +45,9 @@ getOptionLabel ) - const onClick = () => { + const onClick = e => { + e.preventDefault() + e.stopPropagation() dispatch("click") if (readonly) { return @@ -88,7 +90,6 @@ class:is-open={open} aria-haspopup="listbox" on:click={onClick} - use:clickOutside={() => (open = false)} bind:this={button} > {#if fieldIcon} @@ -130,14 +131,17 @@ (open = false)} useAnchorWidth={!autoWidth} maxWidth={autoWidth ? 400 : null} > -
+
(open = false)} + > {#if autocomplete}
+
import "@spectrum-css/link/dist/index-vars.css" + import { createEventDispatcher } from "svelte" export let href = "#" export let size = "M" @@ -9,10 +10,12 @@ export let overBackground = false export let target export let download + + const dispatch = createEventDispatcher() dispatch("click") && e.stopPropagation()} {href} {target} {download} diff --git a/packages/bbui/src/Popover/Popover.svelte b/packages/bbui/src/Popover/Popover.svelte index 191898578a..7eb77d90fa 100644 --- a/packages/bbui/src/Popover/Popover.svelte +++ b/packages/bbui/src/Popover/Popover.svelte @@ -5,6 +5,8 @@ import positionDropdown from "../Actions/position_dropdown" import clickOutside from "../Actions/click_outside" import { fly } from "svelte/transition" + import { getContext } from "svelte" + import Context from "../context" const dispatch = createEventDispatcher() @@ -24,6 +26,7 @@ $: tooltipClasses = showTip ? `spectrum-Popover--withTip spectrum-Popover--${direction}` : "" + $: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum" export const show = () => { dispatch("open") @@ -61,7 +64,7 @@ {#if open} - +
{ - context("Auth Configuration", () => { + xcontext("Auth Configuration", () => { before(() => { cy.login() }) @@ -21,7 +21,7 @@ filterTests(["smoke", "all"], () => { cy.get("[data-cy=oidc-active]").should('not.be.checked') cy.intercept("POST", "/api/global/configs").as("updateAuth") - cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true}) + cy.get("button[data-cy=oidc-save]").contains("Save").click({ force: true }) cy.wait("@updateAuth") cy.get("@updateAuth").its("response.statusCode").should("eq", 200) @@ -45,7 +45,7 @@ filterTests(["smoke", "all"], () => { cy.get("button[data-cy=oidc-save]").should("not.be.disabled"); cy.intercept("POST", "/api/global/configs").as("updateAuth") - cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true}) + cy.get("button[data-cy=oidc-save]").contains("Save").click({ force: true }) cy.wait("@updateAuth") cy.get("@updateAuth").its("response.statusCode").should("eq", 200) @@ -85,11 +85,11 @@ filterTests(["smoke", "all"], () => { cy.get(".auth-form input.spectrum-Textfield-input").type("Another ") cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 6) cy.get(".spectrum-Tags-item").contains("Another") - + cy.get("button[data-cy=oidc-save]").should("not.be.disabled"); cy.intercept("POST", "/api/global/configs").as("updateAuth") - cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true}) + cy.get("button[data-cy=oidc-save]").contains("Save").click({ force: true }) cy.wait("@updateAuth") cy.get("@updateAuth").its("response.statusCode").should("eq", 200) @@ -123,7 +123,7 @@ filterTests(["smoke", "all"], () => { cy.get("button[data-cy=oidc-save]").should("not.be.disabled"); cy.intercept("POST", "/api/global/configs").as("updateAuth") - cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true}) + cy.get("button[data-cy=oidc-save]").contains("Save").click({ force: true }) cy.wait("@updateAuth") cy.get("@updateAuth").its("response.statusCode").should("eq", 200) @@ -144,7 +144,7 @@ filterTests(["smoke", "all"], () => { cy.get("div.content").scrollTo("bottom") - cy.get("[data-cy=restore-oidc-default-scopes]").click({force: true}) + cy.get("[data-cy=restore-oidc-default-scopes]").click({ force: true }) cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) diff --git a/packages/builder/cypress/integration/adminAndManagement/userSettings.spec.js b/packages/builder/cypress/integration/adminAndManagement/userSettings.spec.js index a2b0d32d02..18362031c9 100644 --- a/packages/builder/cypress/integration/adminAndManagement/userSettings.spec.js +++ b/packages/builder/cypress/integration/adminAndManagement/userSettings.spec.js @@ -3,107 +3,112 @@ const interact = require('../../support/interact') filterTests(["smoke", "all"], () => { context("User Settings Menu", () => { - + before(() => { cy.login() }) - + it("should update user information via user settings menu", () => { - const fname = "test" - const lname = "user" + const fname = "test" + const lname = "user" - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.updateUserInformation(fname, lname) + cy.visit(`${Cypress.config().baseUrl}/builder`) + cy.updateUserInformation(fname, lname) - // Go to user info and confirm name update - cy.contains("Users").click() - cy.contains("test@test.com").click() + // Go to user info and confirm name update + cy.contains("Users").click() + cy.contains("test@test.com").click() - cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => { - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', fname) - }) - cy.get(interact.FIELD).eq(2).within(() => { - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', lname) - }) + cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => { + cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', fname) + }) + cy.get(interact.FIELD).eq(2).within(() => { + cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', lname) + }) }) - it("should allow copying of the users API key", () => { - cy.get(".user-dropdown .avatar > .icon", { timeout: 2000 }).click({ force: true }) - cy.get(interact.SPECTRUM_MENU_ITEM).contains("View API key").click({ force: true }) - cy.get(interact.SPECTRUM_DIALOG_CONTENT).within(() => { - cy.get(interact.SPECTRUM_ICON).click({force: true}) - }) - // There may be timing issues with this on the smoke build - cy.wait(500) - cy.get(".spectrum-Toast-content") + xit("should allow copying of the users API key", () => { + cy.get(".user-dropdown .avatar > .icon", { timeout: 2000 }).click({ force: true }) + cy.get(interact.SPECTRUM_MENU_ITEM).contains("View API key").click({ force: true }) + cy.get(interact.SPECTRUM_DIALOG_CONTENT).within(() => { + cy.get(interact.SPECTRUM_ICON).click({ force: true }) + }) + // There may be timing issues with this on the smoke build + cy.wait(500) + cy.get(".spectrum-Toast-content") .contains("URL copied to clipboard") .should("be.visible") }) it("should allow API key regeneration", () => { - // Get initial API key value - cy.get(interact.SPECTRUM_DIALOG_CONTENT) + cy.get(".user-dropdown .icon", { timeout: 2000 }).click({ force: true }) + cy.get(interact.SPECTRUM_MENU_ITEM).contains("View API key").click({ force: true }) + cy.get(interact.SPECTRUM_DIALOG_CONTENT).within(() => { + cy.get(interact.SPECTRUM_ICON).click({ force: true }) + }) + // Get initial API key value + cy.get(interact.SPECTRUM_DIALOG_CONTENT) .find(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').as('keyOne') - // Click re-generate key button - cy.get("button").contains("Re-generate key").click({ force: true }) + // Click re-generate key button + cy.get("button").contains("Regenerate key").click({ force: true }) - // Verify API key was changed - cy.get(interact.SPECTRUM_DIALOG_CONTENT).within(() => { - cy.get('@keyOne').then((keyOne) => { - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').should('not.eq', keyOne) - }) + // Verify API key was changed + cy.get(interact.SPECTRUM_DIALOG_CONTENT).within(() => { + cy.get('@keyOne').then((keyOne) => { + cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').should('not.eq', keyOne) }) - cy.closeModal() + }) + cy.closeModal() }) it("should update password", () => { - // Access Update password modal - cy.get(".user-dropdown .avatar > .icon", { timeout: 2000 }).click({ force: true }) - cy.get(interact.SPECTRUM_MENU_ITEM).contains("Update password").click({ force: true }) + // Access Update password modal + cy.get(".user-dropdown .icon", { timeout: 2000 }).click({ force: true }) + cy.get(interact.SPECTRUM_MENU_ITEM).contains("Update password").click({ force: true }) - // Enter new password and update - cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => { - for (let i = 0; i < 2; i++) { - // password set to 'newpwd' - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("newpwd") - } - cy.get("button").contains("Update password").click({ force: true }) - }) + // Enter new password and update + cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => { + for (let i = 0; i < 2; i++) { + // password set to 'newpwd' + cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("newpwd") + } + cy.get("button").contains("Update password").click({ force: true }) + }) - // Logout & in with new password - //cy.logOut() - cy.login("test@test.com", "newpwd") + // Logout & in with new password + //cy.logOut() + cy.login("test@test.com", "newpwd") }) - it("should open and close developer mode", () => { - cy.get(".user-dropdown .avatar > .icon", { timeout: 2000 }).click({ force: true }) - - // Close developer mode & verify - cy.get(interact.SPECTRUM_MENU_ITEM).contains("Close developer mode").click({ force: true }) - cy.get(interact.SPECTRUM_SIDENAV).should('not.exist') // No config sections - cy.get(interact.CREATE_APP_BUTTON).should('not.exist') // No create app button - cy.get(".app").should('not.exist') // At least one app should be available + xit("should open and close developer mode", () => { + cy.get(".user-dropdown .icon", { timeout: 2000 }).click({ force: true }) - // Open developer mode & verify - cy.get(".avatar > .icon").click({ force: true }) - cy.get(interact.SPECTRUM_MENU_ITEM).contains("Open developer mode").click({ force: true }) - cy.get(interact.SPECTRUM_SIDENAV).should('exist') // config sections available - cy.get(interact.CREATE_APP_BUTTON).should('exist') // create app button available + // Close developer mode & verify + cy.get(interact.SPECTRUM_MENU_ITEM).contains("Close developer mode").click({ force: true }) + cy.get(interact.SPECTRUM_SIDENAV).should('not.exist') // No config sections + cy.get(interact.CREATE_APP_BUTTON).should('not.exist') // No create app button + cy.get(".app").should('not.exist') // At least one app should be available + + // Open developer mode & verify + cy.get(".avatar > .icon").click({ force: true }) + cy.get(interact.SPECTRUM_MENU_ITEM).contains("Open developer mode").click({ force: true }) + cy.get(".app-table").should('exist') // config sections available + cy.get(interact.CREATE_APP_BUTTON).should('exist') // create app button available }) after(() => { - // Change password back to original value - cy.get(".user-dropdown .avatar > .icon", { timeout: 2000 }).click({ force: true }) - cy.get(interact.SPECTRUM_MENU_ITEM).contains("Update password").click({ force: true }) - cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => { - for (let i = 0; i < 2; i++) { - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("test") - } - cy.get("button").contains("Update password").click({ force: true }) - }) - // Remove users name - cy.updateUserInformation() + // Change password back to original value + cy.get(".user-dropdown .icon", { timeout: 2000 }).click({ force: true }) + cy.get(interact.SPECTRUM_MENU_ITEM).contains("Update password").click({ force: true }) + cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => { + for (let i = 0; i < 2; i++) { + cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("test") + } + cy.get("button").contains("Update password").click({ force: true }) }) + // Remove users name + cy.updateUserInformation() + }) }) }) diff --git a/packages/builder/cypress/integration/appOverview.spec.js b/packages/builder/cypress/integration/appOverview.spec.js index 2afc0af277..60d0f2a06d 100644 --- a/packages/builder/cypress/integration/appOverview.spec.js +++ b/packages/builder/cypress/integration/appOverview.spec.js @@ -2,7 +2,7 @@ import filterTests from "../support/filterTests" import clientPackage from "@budibase/client/package.json" filterTests(["all"], () => { - context("Application Overview screen", () => { + xcontext("Application Overview screen", () => { before(() => { cy.login() cy.deleteAllApps() diff --git a/packages/builder/cypress/integration/createApp.spec.js b/packages/builder/cypress/integration/createApp.spec.js index d37b0806c4..897c7f8b91 100644 --- a/packages/builder/cypress/integration/createApp.spec.js +++ b/packages/builder/cypress/integration/createApp.spec.js @@ -14,15 +14,15 @@ filterTests(['smoke', 'all'], () => { cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/create`, { timeout: 5000 }) //added /portal/apps/create cy.wait(1000) cy.get(interact.CREATE_APP_BUTTON, { timeout: 10000 }).contains('Start from scratch').should("exist") - + cy.get(interact.TEMPLATE_CATEGORY_FILTER).should("exist") cy.get(interact.TEMPLATE_CATEGORY).should("exist") - + cy.get(interact.APP_TABLE).should("not.exist") }) } - it("should provide filterable templates", () => { + xit("should provide filterable templates", () => { cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 }) cy.wait(500) @@ -30,16 +30,16 @@ filterTests(['smoke', 'all'], () => { .its("body") .then(val => { if (val.length > 0) { - cy.get(interact.SPECTRUM_BUTTON).contains("Templates").click({force: true}) + cy.get(interact.SPECTRUM_BUTTON).contains("View Templates").click({ force: true }) } }) cy.get(interact.TEMPLATE_CATEGORY_FILTER).should("exist") cy.get(interact.TEMPLATE_CATEGORY).should("exist") - + cy.get(interact.TEMPLATE_CATEGORY_ACTIONGROUP).its('length').should('be.gt', 1) cy.get(interact.TEMPLATE_CATEGORY_FILTER_ACTIONBUTTON).its('length').should('be.gt', 2) - + cy.get(interact.TEMPLATE_CATEGORY_FILTER_ACTIONBUTTON).eq(1).click() cy.get(interact.TEMPLATE_CATEGORY_ACTIONGROUP).should('have.length', 1) @@ -104,14 +104,14 @@ filterTests(['smoke', 'all'], () => { cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 }) cy.updateUserInformation("Ted", "Userman") - + cy.createApp("", false) cy.applicationInAppTable("Teds app") cy.deleteApp("Teds app") // Accomodate names that end in 'S' cy.updateUserInformation("Chris", "Userman") - + cy.createApp("", false) cy.applicationInAppTable("Chris app") cy.deleteApp("Chris app") @@ -123,35 +123,49 @@ filterTests(['smoke', 'all'], () => { const exportedApp = 'cypress/fixtures/exported-app.txt' cy.importApp(exportedApp, "") - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 2000 }) - cy.applicationInAppTable("My app") - - cy.get(".appTable .name").eq(0).click() - - cy.deleteApp("My app") + cy.get(".app-table .name").eq(0).click() + cy.closeModal() + cy.get(`[aria-label="ShowMenu"]`).click() + cy.get(".spectrum-Menu").within(() => { + cy.contains("Overview").click() + }) + cy.get(".app-overview-actions-icon").within(() => { + cy.get(".spectrum-Icon").click({ force: true }) + }) + cy.get(".spectrum-Menu").contains("Delete").click({ force: true }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get("input").type("My app") + }) + cy.get(".spectrum-Button--warning").click() }) it("should create an application from an export, using the users first name as the default app name", () => { const exportedApp = 'cypress/fixtures/exported-app.txt' cy.updateUserInformation("Ted", "Userman") - cy.importApp(exportedApp, "") - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.applicationInAppTable("Teds app") - - cy.get(".appTable .name").eq(0).click() - - cy.deleteApp("Teds app") - + cy.get(".app-table .name").eq(0).click() + cy.closeModal() + cy.get(`[aria-label="ShowMenu"]`).click() + cy.get(".spectrum-Menu").within(() => { + cy.contains("Overview").click() + }) + cy.get(".app-overview-actions-icon").within(() => { + cy.get(".spectrum-Icon").click({ force: true }) + }) + cy.get(".spectrum-Menu").contains("Delete").click({ force: true }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get("input").type("Teds app") + }) + cy.get(".spectrum-Button--warning").click() cy.updateUserInformation("", "") }) - it("should generate the first application from a template", () => { + xit("should generate the first application from a template", () => { cy.visit(`${Cypress.config().baseUrl}/builder`) cy.wait(500) @@ -172,28 +186,28 @@ filterTests(['smoke', 'all'], () => { const card = cy.get('.template-card').eq(0).should("exist"); const cardOverlay = card.get('.template-thumbnail-action-overlay').should("exist") cardOverlay.invoke("show") - cardOverlay.get("button").contains("Use template").should("exist").click({force: true}) + cardOverlay.get("button").contains("Use template").should("exist").click({ force: true }) }) // CMD Create app from theme card cy.get(".spectrum-Modal").should('be.visible') - + const templateName = cy.get(".spectrum-Modal .template-thumbnail-text") templateName.invoke('text') - .then(templateNameText => { - const templateNameParsed = "/"+templateNameText.toLowerCase().replace(/\s+/g, "-") - cy.get(interact.SPECTRUM_MODAL_INPUT).eq(0).should("have.value", templateNameText) - cy.get(interact.SPECTRUM_MODAL_INPUT).eq(1).should("have.value", templateNameParsed) + .then(templateNameText => { + const templateNameParsed = "/" + templateNameText.toLowerCase().replace(/\s+/g, "-") + cy.get(interact.SPECTRUM_MODAL_INPUT).eq(0).should("have.value", templateNameText) + cy.get(interact.SPECTRUM_MODAL_INPUT).eq(1).should("have.value", templateNameParsed) - cy.get(".spectrum-Modal .spectrum-ButtonGroup").contains("Create app").click() - cy.wait(5000) - - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.wait(2000) + cy.get(".spectrum-Modal .spectrum-ButtonGroup").contains("Create app").click() + cy.wait(5000) - cy.applicationInAppTable(templateNameText) - cy.deleteApp(templateNameText) - }); + cy.visit(`${Cypress.config().baseUrl}/builder`) + cy.wait(2000) + + cy.applicationInAppTable(templateNameText) + cy.deleteApp(templateNameText) + }); }) @@ -217,5 +231,5 @@ filterTests(['smoke', 'all'], () => { cy.deleteApp(secondAppName) }) - }) + }) }) diff --git a/packages/builder/cypress/support/commands.js b/packages/builder/cypress/support/commands.js index 4c44fd6672..e63fd41591 100644 --- a/packages/builder/cypress/support/commands.js +++ b/packages/builder/cypress/support/commands.js @@ -101,7 +101,7 @@ Cypress.Commands.add("deleteUser", email => { }) Cypress.Commands.add("updateUserInformation", (firstName, lastName) => { - cy.get(".user-dropdown .avatar > .icon", { timeout: 2000 }).click({ + cy.get(".user-dropdown .icon", { timeout: 2000 }).click({ force: true, }) @@ -132,7 +132,7 @@ Cypress.Commands.add("updateUserInformation", (firstName, lastName) => { .blur() } cy.get(".confirm-wrap").within(() => { - cy.get("button").contains("Update information").click({ force: true }) + cy.get("button").contains("Save").click({ force: true }) }) cy.get(".spectrum-Dialog-grid").should("not.exist") }) @@ -222,9 +222,12 @@ Cypress.Commands.add("deleteApp", name => { // Go to app overview const appIdParsed = appId.split("_").pop() const actionEleId = `[data-cy=row_actions_${appIdParsed}]` - cy.get(actionEleId).within(() => { - cy.contains("Manage").click({ force: true }) + cy.get(actionEleId).click() + cy.get(`[aria-label="ShowMenu"]`).click() + cy.get(".spectrum-Menu").within(() => { + cy.contains("Overview").click() }) + cy.wait(500) // Unpublish first if needed @@ -400,7 +403,7 @@ Cypress.Commands.add("searchForApplication", appName => { return } else { // Searches for the app - cy.get(".filter").then(() => { + cy.get(".spectrum-Search").then(() => { cy.get(".spectrum-Textfield").within(() => { cy.get("input").eq(0).clear({ force: true }) cy.get("input").eq(0).type(appName, { force: true }) @@ -413,7 +416,7 @@ Cypress.Commands.add("searchForApplication", appName => { // Assumes there are no others Cypress.Commands.add("applicationInAppTable", appName => { cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 }) - cy.get(".appTable", { timeout: 30000 }).within(() => { + cy.get(".app-table", { timeout: 30000 }).within(() => { cy.get(".title").contains(appName).should("exist") }) }) diff --git a/packages/builder/package.json b/packages/builder/package.json index 3c91ee1b48..68ac91264c 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "2.2.12-alpha.21", + "version": "2.2.12-alpha.41", "license": "GPL-3.0", "private": true, "scripts": { @@ -71,11 +71,12 @@ } }, "dependencies": { - "@budibase/bbui": "2.2.12-alpha.21", - "@budibase/client": "2.2.12-alpha.21", - "@budibase/frontend-core": "2.2.12-alpha.21", - "@budibase/string-templates": "2.2.12-alpha.21", + "@budibase/bbui": "2.2.12-alpha.41", + "@budibase/client": "2.2.12-alpha.41", + "@budibase/frontend-core": "2.2.12-alpha.41", + "@budibase/string-templates": "2.2.12-alpha.41", "@sentry/browser": "5.19.1", + "@spectrum-css/accordion": "^3.0.24", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", "codemirror": "^5.59.0", diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index 122cb0d519..b8fa22e51b 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -398,6 +398,7 @@ const getProviderContextBindings = (asset, dataProviders) => { providerId, // Table ID is used by JSON fields to know what table the field is in tableId: table?._id, + component: component._component, category: component._instanceName, icon: def.icon, display: { diff --git a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte index 3ea75ffac3..45dc75a1fe 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte @@ -8,6 +8,7 @@ TextArea, Modal, EnvDropdown, + Accordion, } from "@budibase/bbui" import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte" import { capitalise } from "helpers" @@ -59,15 +60,24 @@ let addButton - function getDisplayName(key) { + function getDisplayName(key, fieldKey) { let name - if (schema[key]?.display) { + if (fieldKey && schema[key]["fields"][fieldKey]?.display) { + name = schema[key]["fields"][fieldKey].display + } else if (fieldKey) { + name = fieldKey + } else if (schema[key]?.display) { name = schema[key].display } else { name = key } return capitalise(name) } + function getFieldGroupKeys(fieldGroup) { + return Object.entries(schema[fieldGroup].fields || {}) + .filter(el => filter(el)) + .map(([key]) => key) + } function save(data) { environment.createVariable(data) @@ -135,6 +145,27 @@ error={$validation.errors[configKey]} />
+ {:else if schema[configKey].type === "fieldGroup"} + !!config[fieldKey] + )} + header={getDisplayName(configKey)} + > + + {#each getFieldGroupKeys(configKey) as fieldKey} +
+ + +
+ {/each} +
+
{:else}
diff --git a/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte b/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte index ec39cc6d71..e43437d756 100644 --- a/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte +++ b/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte @@ -10,7 +10,6 @@ } from "@budibase/bbui" import { tables } from "stores/backend" import { Helpers } from "@budibase/bbui" - import { writable } from "svelte/store" export let save export let datasource @@ -18,41 +17,97 @@ export let fromRelationship = {} export let toRelationship = {} export let close - export let selectedFromTable - let originalFromName = fromRelationship.name, - originalToName = toRelationship.name - let fromTable, toTable, through, linkTable, tableOptions - let isManyToMany, isManyToOne, relationshipTypes - let errors, valid - let currentTables = {} + const colNotSet = "Please specify a column name" + const relationshipAlreadyExists = + "A relationship between these tables already exists." + const relationshipTypes = [ + { + label: "One to Many", + value: RelationshipTypes.MANY_TO_ONE, + }, + { + label: "Many to Many", + value: RelationshipTypes.MANY_TO_MANY, + }, + ] - if (fromRelationship && !fromRelationship.relationshipType) { - fromRelationship.relationshipType = RelationshipTypes.MANY_TO_ONE - } + let originalFromColumnName = toRelationship.name, + originalToColumnName = fromRelationship.name + let originalFromTable = plusTables.find( + table => table._id === toRelationship?.tableId + ) + let originalToTable = plusTables.find( + table => table._id === fromRelationship?.tableId + ) - if (toRelationship && selectedFromTable) { - toRelationship.tableId = selectedFromTable._id - } + let tableOptions + let errors = {} + let hasClickedSave = !!fromRelationship.relationshipType + let fromPrimary, + fromForeign, + fromTable, + toTable, + throughTable, + fromColumn, + toColumn + let fromId, toId, throughId, throughToKey, throughFromKey + let isManyToMany, isManyToOne, relationshipType - function inSchema(table, prop, ogName) { - if (!table || !prop || prop === ogName) { - return false + $: { + if (!fromPrimary) { + fromPrimary = fromRelationship.foreignKey + fromForeign = toRelationship.foreignKey + } + if (!fromColumn && !errors.fromColumn) { + fromColumn = toRelationship.name + } + if (!toColumn && !errors.toColumn) { + toColumn = fromRelationship.name + } + if (!fromId) { + fromId = toRelationship.tableId + } + if (!toId) { + toId = fromRelationship.tableId + } + if (!throughId) { + throughId = fromRelationship.through + throughFromKey = fromRelationship.throughFrom + throughToKey = fromRelationship.throughTo + } + if (!relationshipType) { + relationshipType = fromRelationship.relationshipType } - const keys = Object.keys(table.schema).map(key => key.toLowerCase()) - return keys.indexOf(prop.toLowerCase()) !== -1 } - const touched = writable({}) + $: tableOptions = plusTables.map(table => ({ + label: table.name, + value: table._id, + })) + $: valid = getErrorCount(errors) === 0 || !hasClickedSave - function invalidThroughTable({ through, throughTo, throughFrom }) { + $: isManyToMany = relationshipType === RelationshipTypes.MANY_TO_MANY + $: isManyToOne = relationshipType === RelationshipTypes.MANY_TO_ONE + $: fromTable = plusTables.find(table => table._id === fromId) + $: toTable = plusTables.find(table => table._id === toId) + $: throughTable = plusTables.find(table => table._id === throughId) + + $: toRelationship.relationshipType = fromRelationship?.relationshipType + + const getErrorCount = errors => + Object.entries(errors) + .filter(entry => !!entry[1]) + .map(entry => entry[0]).length + + function invalidThroughTable() { // need to know the foreign key columns to check error - if (!through || !throughTo || !throughFrom) { + if (!throughId || !throughToKey || !throughFromKey) { return false } - const throughTable = plusTables.find(tbl => tbl._id === through) - const otherColumns = Object.values(throughTable.schema).filter( - col => col.name !== throughFrom && col.name !== throughTo + const throughTbl = plusTables.find(tbl => tbl._id === throughId) + const otherColumns = Object.values(throughTbl.schema).filter( + col => col.name !== throughFromKey && col.name !== throughToKey ) for (let col of otherColumns) { if (col.constraints?.presence && !col.autocolumn) { @@ -62,142 +117,138 @@ return false } - function checkForErrors(fromRelate, toRelate) { - const isMany = - fromRelate.relationshipType === RelationshipTypes.MANY_TO_MANY + function validate() { + const isMany = relationshipType === RelationshipTypes.MANY_TO_MANY const tableNotSet = "Please specify a table" + const foreignKeyNotSet = "Please pick a foreign key" const errObj = {} - if ($touched.from && !fromTable) { - errObj.from = tableNotSet + if (!relationshipType) { + errObj.relationshipType = "Please specify a relationship type" } - if ($touched.to && !toTable) { - errObj.to = tableNotSet + if (!fromTable) { + errObj.fromTable = tableNotSet } - if ($touched.through && isMany && !fromRelate.through) { - errObj.through = tableNotSet + if (!toTable) { + errObj.toTable = tableNotSet } - if ($touched.through && invalidThroughTable(fromRelate)) { - errObj.through = - "Ensure all columns in table are nullable or auto generated" + if (isMany && !throughTable) { + errObj.throughTable = tableNotSet } - if ($touched.foreign && !isMany && !fromRelate.fieldName) { - errObj.foreign = "Please pick the foreign key" + if (isMany && !throughFromKey) { + errObj.throughFromKey = foreignKeyNotSet } - const colNotSet = "Please specify a column name" - if ($touched.fromCol && !fromRelate.name) { - errObj.fromCol = colNotSet + if (isMany && !throughToKey) { + errObj.throughToKey = foreignKeyNotSet } - if ($touched.toCol && !toRelate.name) { - errObj.toCol = colNotSet + if (invalidThroughTable()) { + errObj.throughTable = + "Ensure non-key columns are nullable or auto-generated" } - if ($touched.primary && !fromPrimary) { - errObj.primary = "Please pick the primary key" + if (!isMany && !fromForeign) { + errObj.fromForeign = foreignKeyNotSet } + if (!fromColumn) { + errObj.fromColumn = colNotSet + } + if (!toColumn) { + errObj.toColumn = colNotSet + } + if (!isMany && !fromPrimary) { + errObj.fromPrimary = "Please pick the primary key" + } + if (isMany && relationshipExists()) { + errObj.fromTable = relationshipAlreadyExists + errObj.toTable = relationshipAlreadyExists + } + // currently don't support relationships back onto the table itself, needs to relate out const tableError = "From/to/through tables must be different" - if (fromTable && (fromTable === toTable || fromTable === through)) { - errObj.from = tableError + if (fromTable && (fromTable === toTable || fromTable === throughTable)) { + errObj.fromTable = tableError } - if (toTable && (toTable === fromTable || toTable === through)) { - errObj.to = tableError + if (toTable && (toTable === fromTable || toTable === throughTable)) { + errObj.toTable = tableError } - if (through && (through === fromTable || through === toTable)) { - errObj.through = tableError + if ( + throughTable && + (throughTable === fromTable || throughTable === toTable) + ) { + errObj.throughTable = tableError } const colError = "Column name cannot be an existing column" - if (inSchema(fromTable, fromRelate.name, originalFromName)) { - errObj.fromCol = colError + if (isColumnNameBeingUsed(toTable, fromColumn, originalFromColumnName)) { + errObj.fromColumn = colError } - if (inSchema(toTable, toRelate.name, originalToName)) { - errObj.toCol = colError + if (isColumnNameBeingUsed(fromTable, toColumn, originalToColumnName)) { + errObj.toColumn = colError } let fromType, toType - if (fromPrimary && fromRelate.fieldName) { + if (fromPrimary && fromForeign) { fromType = fromTable?.schema[fromPrimary]?.type - toType = toTable?.schema[fromRelate.fieldName]?.type + toType = toTable?.schema[fromForeign]?.type } if (fromType && toType && fromType !== toType) { - errObj.foreign = + errObj.fromForeign = "Column type of the foreign key must match the primary key" } + errors = errObj + return getErrorCount(errors) === 0 } - let fromPrimary - $: { - if (!fromPrimary && fromTable) { - fromPrimary = fromTable.primary[0] - } - } - $: isManyToMany = - fromRelationship?.relationshipType === RelationshipTypes.MANY_TO_MANY - $: isManyToOne = - fromRelationship?.relationshipType === RelationshipTypes.MANY_TO_ONE - $: tableOptions = plusTables.map(table => ({ - label: table.name, - value: table._id, - })) - $: fromTable = plusTables.find(table => table._id === toRelationship?.tableId) - $: toTable = plusTables.find(table => table._id === fromRelationship?.tableId) - $: through = plusTables.find(table => table._id === fromRelationship?.through) - $: checkForErrors(fromRelationship, toRelationship) - $: valid = - Object.keys(errors).length === 0 && Object.keys($touched).length !== 0 - $: linkTable = through || toTable - $: relationshipTypes = [ - { - label: "Many", - value: RelationshipTypes.MANY_TO_MANY, - }, - { - label: "One", - value: RelationshipTypes.MANY_TO_ONE, - }, - ] - $: updateRelationshipType(fromRelationship?.relationshipType) - $: tableChanged(fromTable, toTable) - - function updateRelationshipType(fromType) { - if (fromType === RelationshipTypes.MANY_TO_MANY) { - toRelationship.relationshipType = RelationshipTypes.MANY_TO_MANY - } else { - toRelationship.relationshipType = RelationshipTypes.MANY_TO_ONE + function isColumnNameBeingUsed(table, columnName, originalName) { + if (!table || !columnName || columnName === originalName) { + return false } + const keys = Object.keys(table.schema).map(key => key.toLowerCase()) + return keys.indexOf(columnName.toLowerCase()) !== -1 } function buildRelationships() { - // if any to many only need to check from - const manyToMany = - fromRelationship.relationshipType === RelationshipTypes.MANY_TO_MANY - // main is simply used to know this is the side the user configured it from const id = Helpers.uuid() - if (!manyToMany) { - delete fromRelationship.through - delete toRelationship.through - } + //Map temporary variables let relateFrom = { ...fromRelationship, + tableId: toId, + name: toColumn, + relationshipType, + fieldName: fromForeign, + through: throughId, + throughFrom: throughFromKey, + throughTo: throughToKey, type: "link", main: true, _id: id, } - let relateTo = { + let relateTo = (toRelationship = { ...toRelationship, + tableId: fromId, + name: fromColumn, + through: throughId, type: "link", _id: id, + }) + + // if any to many only need to check from + const manyToMany = + relateFrom.relationshipType === RelationshipTypes.MANY_TO_MANY + + if (!manyToMany) { + delete relateFrom.through + delete relateTo.through } // [0] is because we don't support composite keys for relationships right now if (manyToMany) { relateFrom = { ...relateFrom, - through: through._id, + through: throughTable._id, fieldName: toTable.primary[0], } relateTo = { ...relateTo, - through: through._id, + through: throughTable._id, fieldName: fromTable.primary[0], throughFrom: relateFrom.throughTo, throughTo: relateFrom.throughFrom, @@ -226,9 +277,56 @@ toRelationship = relateTo } - // save the relationship on to the datasource + function relationshipExists() { + if ( + originalFromTable && + originalToTable && + originalFromTable === fromTable && + originalToTable === toTable + ) { + return false + } + let fromThroughLinks = Object.values( + datasource.entities[fromTable.name].schema + ).filter(value => value.through) + let toThroughLinks = Object.values( + datasource.entities[toTable.name].schema + ).filter(value => value.through) + + const matchAgainstUserInput = (fromTableId, toTableId) => + (fromTableId === fromId && toTableId === toId) || + (fromTableId === toId && toTableId === fromId) + + return !!fromThroughLinks.find(from => + toThroughLinks.find( + to => + from.through === to.through && + matchAgainstUserInput(from.tableId, to.tableId) + ) + ) + } + + function removeExistingRelationship() { + if (originalFromTable && originalFromColumnName) { + delete datasource.entities[originalFromTable.name].schema[ + originalToColumnName + ] + } + if (originalToTable && originalToColumnName) { + delete datasource.entities[originalToTable.name].schema[ + originalFromColumnName + ] + } + } + async function saveRelationship() { + hasClickedSave = true + if (!validate()) { + return false + } buildRelationships() + removeExistingRelationship() + // source of relationship datasource.entities[fromTable.name].schema[fromRelationship.name] = fromRelationship @@ -236,43 +334,14 @@ datasource.entities[toTable.name].schema[toRelationship.name] = toRelationship - // If relationship has been renamed - if (originalFromName !== fromRelationship.name) { - delete datasource.entities[fromTable.name].schema[originalFromName] - } - if (originalToName !== toRelationship.name) { - delete datasource.entities[toTable.name].schema[originalToName] - } - - // store the original names so it won't cause an error - originalToName = toRelationship.name - originalFromName = fromRelationship.name await save() } - async function deleteRelationship() { - delete datasource.entities[fromTable.name].schema[fromRelationship.name] - delete datasource.entities[toTable.name].schema[toRelationship.name] + removeExistingRelationship() await save() await tables.fetch() close() } - - function tableChanged(fromTbl, toTbl) { - if ( - (currentTables?.from?._id === fromTbl?._id && - currentTables?.to?._id === toTbl?._id) || - originalFromName || - originalToName - ) { - return - } - fromRelationship.name = toTbl?.name || "" - errors.fromCol = "" - toRelationship.name = fromTbl?.name || "" - errors.toCol = "" - currentTables = { from: fromTbl, to: toTbl } - } (errors.relationshipType = null)} />
Tables @@ -292,58 +363,89 @@ ($touched.primary = true)} - bind:error={errors.primary} + label={`Primary Key (${fromTable.name})`} + options={Object.keys(fromTable.schema)} bind:value={fromPrimary} + bind:error={errors.fromPrimary} + on:change={() => (errors.fromPrimary = null)} /> {/if} ($touched.through = true)} - bind:error={errors.through} - bind:value={fromRelationship.through} + bind:value={throughId} + bind:error={errors.throughTable} + on:change={() => { + errors.fromTable = null + errors.toTable = null + errors.throughTable = null + }} /> - {#if fromTable && toTable && through} + {#if fromTable && toTable && throughTable} ($touched.toForeign = true)} - bind:error={errors.toForeign} - bind:value={fromRelationship.throughFrom} + options={Object.keys(throughTable?.schema)} + bind:value={throughFromKey} + bind:error={errors.throughFromKey} + on:change={e => { + if (throughToKey === e.detail) { + throughToKey = null + } + errors.throughFromKey = null + }} /> {/if} {:else if isManyToOne && toTable} ($touched.fromCol = true)} - bind:error={errors.fromCol} label="From table column" - bind:value={fromRelationship.name} + bind:value={fromColumn} + bind:error={errors.fromColumn} + on:change={e => { + errors.fromColumn = e.detail?.length > 0 ? null : colNotSet + }} /> ($touched.toCol = true)} - bind:error={errors.toCol} label="To table column" - bind:value={toRelationship.name} + bind:value={toColumn} + bind:error={errors.toColumn} + on:change={e => (errors.toColumn = e.detail?.length > 0 ? null : colNotSet)} />
- {#if originalFromName != null} + {#if originalFromColumnName != null} {/if}
diff --git a/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte b/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte index 6269599f40..49151a09b0 100644 --- a/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte +++ b/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte @@ -70,7 +70,10 @@ type: "provider", })) $: links = bindings + // Get only link bindings .filter(x => x.fieldSchema?.type === "link") + // Filter out bindings provided by forms + .filter(x => !x.component?.endsWith("/form")) .map(binding => { const { providerId, readableBinding, fieldSchema } = binding || {} const { name, tableId } = fieldSchema || {} diff --git a/packages/builder/src/components/portal/page/SideNavItem.svelte b/packages/builder/src/components/portal/page/SideNavItem.svelte index b31afb683f..c22b8b6113 100644 --- a/packages/builder/src/components/portal/page/SideNavItem.svelte +++ b/packages/builder/src/components/portal/page/SideNavItem.svelte @@ -5,7 +5,7 @@
- {text} + {text || ""} diff --git a/packages/frontend-core/src/components/TestimonialPage.svelte b/packages/frontend-core/src/components/TestimonialPage.svelte new file mode 100644 index 0000000000..f2f74731d0 --- /dev/null +++ b/packages/frontend-core/src/components/TestimonialPage.svelte @@ -0,0 +1,63 @@ + + + + +
+
+ +
+ "Here is an example of how Budibase changed my life for the better and + now all I do is eat, sleep, build apps, repeat." +
+
+ a-happy-budibase-user +
+
No-code Enthusiast
+
Bedroom TLD
+
+
+
+
+
+
+ + diff --git a/packages/frontend-core/src/components/index.js b/packages/frontend-core/src/components/index.js new file mode 100644 index 0000000000..7ca21c4ff9 --- /dev/null +++ b/packages/frontend-core/src/components/index.js @@ -0,0 +1,2 @@ +export { default as SplitPage } from "./SplitPage.svelte" +export { default as TestimonialPage } from "./TestimonialPage.svelte" diff --git a/packages/frontend-core/src/index.js b/packages/frontend-core/src/index.js index aecca81cda..01bf05c69e 100644 --- a/packages/frontend-core/src/index.js +++ b/packages/frontend-core/src/index.js @@ -3,3 +3,4 @@ export { fetchData } from "./fetch/fetchData" export * as Constants from "./constants" export * from "./stores" export * from "./utils" +export * from "./components" diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 91644678a9..2e51566f8c 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/sdk", - "version": "2.2.12-alpha.21", + "version": "2.2.12-alpha.41", "description": "Budibase Public API SDK", "author": "Budibase", "license": "MPL-2.0", diff --git a/packages/sdk/yarn.lock b/packages/sdk/yarn.lock index 4e8d6d718f..c3106e4ed7 100644 --- a/packages/sdk/yarn.lock +++ b/packages/sdk/yarn.lock @@ -225,9 +225,9 @@ concat-map@0.0.1: integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== cookiejar@^2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.3.tgz#fc7a6216e408e74414b90230050842dacda75acc" - integrity sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ== + version "2.1.4" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" + integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== debug@^4.1.1: version "4.3.4" diff --git a/packages/server/package.json b/packages/server/package.json index 8210cf4cba..eb6ac3def1 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "2.2.12-alpha.21", + "version": "2.2.12-alpha.41", "description": "Budibase Web Server", "main": "src/index.ts", "repository": { @@ -43,11 +43,11 @@ "license": "GPL-3.0", "dependencies": { "@apidevtools/swagger-parser": "10.0.3", - "@budibase/backend-core": "2.2.12-alpha.21", - "@budibase/client": "2.2.12-alpha.21", - "@budibase/pro": "2.2.12-alpha.21", - "@budibase/string-templates": "2.2.12-alpha.21", - "@budibase/types": "2.2.12-alpha.21", + "@budibase/backend-core": "2.2.12-alpha.41", + "@budibase/client": "2.2.12-alpha.41", + "@budibase/pro": "2.2.12-alpha.41", + "@budibase/string-templates": "2.2.12-alpha.41", + "@budibase/types": "2.2.12-alpha.41", "@bull-board/api": "3.7.0", "@bull-board/koa": "3.9.4", "@elastic/elasticsearch": "7.10.0", @@ -77,7 +77,7 @@ "joi": "17.6.0", "js-yaml": "4.1.0", "jsonschema": "1.4.0", - "knex": "0.95.15", + "knex": "2.4.0", "koa": "2.13.4", "koa-body": "4.2.0", "koa-compress": "4.0.1", diff --git a/packages/server/src/api/controllers/row/ExternalRequest.ts b/packages/server/src/api/controllers/row/ExternalRequest.ts index f5ac028ead..81b698fedd 100644 --- a/packages/server/src/api/controllers/row/ExternalRequest.ts +++ b/packages/server/src/api/controllers/row/ExternalRequest.ts @@ -681,6 +681,12 @@ export class ExternalRequest { config, table ) + //if the sort column is a formula, remove it + for (let sortColumn of Object.keys(sort || {})) { + if (table.schema[sortColumn]?.type === "formula") { + delete sort?.[sortColumn] + } + } filters = buildFilters(id, filters || {}, table) const relationships = this.buildRelationships(table) // clean up row on ingress using schema diff --git a/packages/server/src/automations/tests/loop.spec.ts b/packages/server/src/automations/tests/loop.spec.ts new file mode 100644 index 0000000000..b64f7b16f8 --- /dev/null +++ b/packages/server/src/automations/tests/loop.spec.ts @@ -0,0 +1,45 @@ +import * as automation from "../index" +import * as triggers from "../triggers" +import { loopAutomation } from "../../tests/utilities/structures" +import { context } from "@budibase/backend-core" +import * as setup from "./utilities" + +describe("Attempt to run a basic loop automation", () => { + let config = setup.getConfig(), + table: any, + row: any + + beforeEach(async () => { + await automation.init() + await config.init() + table = await config.createTable() + row = await config.createRow() + }) + + afterAll(setup.afterAll) + + async function runLoop(loopOpts?: any) { + const appId = config.getAppId() + return await context.doInAppContext(appId, async () => { + const params = { fields: { appId } } + return await triggers.externalTrigger( + loopAutomation(table._id, loopOpts), + params, + { getResponses: true } + ) + }) + } + + it("attempt to run a basic loop", async () => { + const resp = await runLoop() + expect(resp.steps[2].outputs.iterations).toBe(1) + }) + + it("test a loop with a string", async () => { + const resp = await runLoop({ + type: "String", + binding: "a,b,c", + }) + expect(resp.steps[2].outputs.iterations).toBe(3) + }) +}) diff --git a/packages/server/src/automations/triggers.ts b/packages/server/src/automations/triggers.ts index f4b34bc9e8..78f8a87b0c 100644 --- a/packages/server/src/automations/triggers.ts +++ b/packages/server/src/automations/triggers.ts @@ -109,8 +109,13 @@ export async function externalTrigger( } params.fields = coercedFields } - const data = { automation, event: params } + const data: Record = { automation, event: params } if (getResponses) { + data.event = { + ...data.event, + appId: context.getAppId(), + automation, + } return utils.processEvent({ data }) } else { return automationQueue.add(data, JOB_OPTS) diff --git a/packages/server/src/integrations/mongodb.ts b/packages/server/src/integrations/mongodb.ts index 9ccefbda58..38b3891fe4 100644 --- a/packages/server/src/integrations/mongodb.ts +++ b/packages/server/src/integrations/mongodb.ts @@ -12,11 +12,16 @@ import { FindOneAndUpdateOptions, UpdateOptions, OperationOptions, + MongoClientOptions, } from "mongodb" +import environment from "../environment" interface MongoDBConfig { connectionString: string db: string + tlsCertificateFile: string + tlsCertificateKeyFile: string + tlsCAFile: string } interface MongoDBQuery { @@ -26,292 +31,331 @@ interface MongoDBQuery { } } -const SCHEMA: Integration = { - docs: "https://github.com/mongodb/node-mongodb-native", - friendlyName: "MongoDB", - type: "Non-relational", - description: - "MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era.", - datasource: { - connectionString: { - type: DatasourceFieldType.STRING, - required: true, - default: "mongodb://localhost:27017", - }, - db: { - type: DatasourceFieldType.STRING, - required: true, - }, - }, - query: { - create: { - type: QueryType.JSON, - }, - read: { - type: QueryType.JSON, - }, - update: { - type: QueryType.JSON, - }, - delete: { - type: QueryType.JSON, - }, - aggregate: { - type: QueryType.JSON, - readable: true, - steps: [ - { - key: "$addFields", - template: "{\n\t\n}", - }, - { - key: "$bucket", - template: `{ - "groupBy": "", - "boundaries": [], - "default": "", - "output": {} -}`, - }, - { - key: "$bucketAuto", - template: `{ - "groupBy": "", - "buckets": 1, - "output": {}, - "granularity": "R5" -}`, - }, - { - key: "$changeStream", - template: `{ - "allChangesForCluster": true, - "fullDocument": "", - "fullDocumentBeforeChange": "", - "resumeAfter": 1, - "showExpandedEvents": true, - "startAfter": {}, - "startAtOperationTime": "" -}`, - }, - { - key: "$collStats", - template: `{ - "latencyStats": { "histograms": true } }, - "storageStats": { "scale": 1 } }, - "count": {}, - "queryExecStats": {} -}`, - }, - { - key: "$count", - template: ``, - }, - { - key: "$densify", - template: `{ - "field": "", - "partitionByFields": [], - "range": { - "step": 1, - "unit": 1, - "bounds": "full" - } -}`, - }, - { - key: "$documents", - template: `[]`, - }, - { - key: "$facet", - template: `{\n\t\n}`, - }, - { - key: "$fill", - template: `{ - "partitionBy": "", - "partitionByFields": [], - "sortBy": {}, - "output": {} -}`, - }, - { - key: "$geoNear", - template: `{ - "near": { - "type": "Point", - "coordinates": [ - -73.98142, 40.71782 - ] - }, - "key": "location", - "distanceField": "dist.calculated", - "query": { "category": "Parks" } -}`, - }, - { - key: "$graphLookup", - template: `{ - "from": "", - "startWith": "", - "connectFromField": "", - "connectToField": "", - "as": "", - "maxDepth": 1, - "depthField": "", - "restrictSearchWithMatch": {} -}`, - }, - { - key: "$group", - template: `{ - "_id": "" -}`, - }, - { - key: "$indexStats", - template: "{\n\t\n}", - }, - { - key: "$limit", - template: `1`, - }, - { - key: "$listLocalSessions", - template: `{\n\t\n}`, - }, - { - key: "$listSessions", - template: `{\n\t\n}`, - }, - { - key: "$lookup", - template: `{ - "from": "", - "localField": "", - "foreignField": "", - "as": "" -}`, - }, - { - key: "$match", - template: "{\n\t\n}", - }, - { - key: "$merge", - template: `{ - "into": {}, - "on": "_id", - "whenMatched": "replace", - "whenNotMatched": "insert" -}`, - }, - { - key: "$out", - template: `{ - "db": "", - "coll": "" -}`, - }, - { - key: "$planCacheStats", - template: "{\n\t\n}", - }, - { - key: "$project", - template: "{\n\t\n}", - }, - { - key: "$redact", - template: "", - }, - { - key: "$replaceRoot", - template: `{ "newRoot": "" }`, - }, - { - key: "$replaceWith", - template: ``, - }, - { - key: "$sample", - template: `{ "size": 3 }`, - }, - { - key: "$set", - template: "{\n\t\n}", - }, - { - key: "$setWindowFields", - template: `{ - "partitionBy": "", - "sortBy": {}, - "output": {} -}`, - }, - { - key: "$skip", - template: `1`, - }, - { - key: "$sort", - template: "{\n\t\n}", - }, - { - key: "$sortByCount", - template: "", - }, - { - key: "$unionWith", - template: `{ - "coll": "", - "pipeline": [] -}`, - }, - { - key: "$unset", - template: "", - }, - { - key: "$unwind", - template: `{ - "path": "", - "includeArrayIndex": "", - "preserveNullAndEmptyArrays": true -}`, - }, - ], - }, - }, - extra: { - collection: { - displayName: "Collection", - type: DatasourceFieldType.STRING, - required: true, - }, - actionType: { - displayName: "Query Type", - type: DatasourceFieldType.LIST, - required: true, - data: { - read: ["find", "findOne", "findOneAndUpdate", "count", "distinct"], - create: ["insertOne", "insertMany"], - update: ["updateOne", "updateMany"], - delete: ["deleteOne", "deleteMany"], - aggregate: ["json", "pipeline"], +const getSchema = () => { + let schema = { + docs: "https://github.com/mongodb/node-mongodb-native", + friendlyName: "MongoDB", + type: "Non-relational", + description: + "MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era.", + datasource: { + connectionString: { + type: DatasourceFieldType.STRING, + required: true, + default: "mongodb://localhost:27017", + display: "Connection string", + }, + db: { + type: DatasourceFieldType.STRING, + required: true, + display: "DB", }, }, - }, + query: { + create: { + type: QueryType.JSON, + }, + read: { + type: QueryType.JSON, + }, + update: { + type: QueryType.JSON, + }, + delete: { + type: QueryType.JSON, + }, + aggregate: { + type: QueryType.JSON, + readable: true, + steps: [ + { + key: "$addFields", + template: "{\n\t\n}", + }, + { + key: "$bucket", + template: `{ + "groupBy": "", + "boundaries": [], + "default": "", + "output": {} + }`, + }, + { + key: "$bucketAuto", + template: `{ + "groupBy": "", + "buckets": 1, + "output": {}, + "granularity": "R5" + }`, + }, + { + key: "$changeStream", + template: `{ + "allChangesForCluster": true, + "fullDocument": "", + "fullDocumentBeforeChange": "", + "resumeAfter": 1, + "showExpandedEvents": true, + "startAfter": {}, + "startAtOperationTime": "" + }`, + }, + { + key: "$collStats", + template: `{ + "latencyStats": { "histograms": true } }, + "storageStats": { "scale": 1 } }, + "count": {}, + "queryExecStats": {} + }`, + }, + { + key: "$count", + template: ``, + }, + { + key: "$densify", + template: `{ + "field": "", + "partitionByFields": [], + "range": { + "step": 1, + "unit": 1, + "bounds": "full" + } + }`, + }, + { + key: "$documents", + template: `[]`, + }, + { + key: "$facet", + template: `{\n\t\n}`, + }, + { + key: "$fill", + template: `{ + "partitionBy": "", + "partitionByFields": [], + "sortBy": {}, + "output": {} + }`, + }, + { + key: "$geoNear", + template: `{ + "near": { + "type": "Point", + "coordinates": [ + -73.98142, 40.71782 + ] + }, + "key": "location", + "distanceField": "dist.calculated", + "query": { "category": "Parks" } + }`, + }, + { + key: "$graphLookup", + template: `{ + "from": "", + "startWith": "", + "connectFromField": "", + "connectToField": "", + "as": "", + "maxDepth": 1, + "depthField": "", + "restrictSearchWithMatch": {} + }`, + }, + { + key: "$group", + template: `{ + "_id": "" + }`, + }, + { + key: "$indexStats", + template: "{\n\t\n}", + }, + { + key: "$limit", + template: `1`, + }, + { + key: "$listLocalSessions", + template: `{\n\t\n}`, + }, + { + key: "$listSessions", + template: `{\n\t\n}`, + }, + { + key: "$lookup", + template: `{ + "from": "", + "localField": "", + "foreignField": "", + "as": "" + }`, + }, + { + key: "$match", + template: "{\n\t\n}", + }, + { + key: "$merge", + template: `{ + "into": {}, + "on": "_id", + "whenMatched": "replace", + "whenNotMatched": "insert" + }`, + }, + { + key: "$out", + template: `{ + "db": "", + "coll": "" + }`, + }, + { + key: "$planCacheStats", + template: "{\n\t\n}", + }, + { + key: "$project", + template: "{\n\t\n}", + }, + { + key: "$redact", + template: "", + }, + { + key: "$replaceRoot", + template: `{ "newRoot": "" }`, + }, + { + key: "$replaceWith", + template: ``, + }, + { + key: "$sample", + template: `{ "size": 3 }`, + }, + { + key: "$set", + template: "{\n\t\n}", + }, + { + key: "$setWindowFields", + template: `{ + "partitionBy": "", + "sortBy": {}, + "output": {} + }`, + }, + { + key: "$skip", + template: `1`, + }, + { + key: "$sort", + template: "{\n\t\n}", + }, + { + key: "$sortByCount", + template: "", + }, + { + key: "$unionWith", + template: `{ + "coll": "", + "pipeline": [] + }`, + }, + { + key: "$unset", + template: "", + }, + { + key: "$unwind", + template: `{ + "path": "", + "includeArrayIndex": "", + "preserveNullAndEmptyArrays": true + }`, + }, + ], + }, + }, + extra: { + collection: { + displayName: "Collection", + type: DatasourceFieldType.STRING, + required: true, + }, + actionType: { + displayName: "Query Type", + type: DatasourceFieldType.LIST, + required: true, + data: { + read: ["find", "findOne", "findOneAndUpdate", "count", "distinct"], + create: ["insertOne", "insertMany"], + update: ["updateOne", "updateMany"], + delete: ["deleteOne", "deleteMany"], + aggregate: ["json", "pipeline"], + }, + }, + }, + } + if (environment.SELF_HOSTED) { + schema.datasource = { + ...schema.datasource, + //@ts-ignore + tls: { + type: DatasourceFieldType.FIELD_GROUP, + display: "Configure SSL", + fields: { + tlsCertificateFile: { + type: DatasourceFieldType.STRING, + required: false, + display: "Certificate file path", + }, + tlsCertificateKeyFile: { + type: DatasourceFieldType.STRING, + required: false, + display: "Certificate Key file path", + }, + tlsCAFile: { + type: DatasourceFieldType.STRING, + required: false, + display: "CA file path", + }, + }, + }, + } + } + return schema } +const SCHEMA: Integration = getSchema() + class MongoIntegration implements IntegrationBase { private config: MongoDBConfig private client: any constructor(config: MongoDBConfig) { this.config = config - this.client = new MongoClient(config.connectionString) + const options: MongoClientOptions = { + tlsCertificateFile: config.tlsCertificateFile || undefined, + tlsCertificateKeyFile: config.tlsCertificateKeyFile || undefined, + tlsCAFile: config.tlsCAFile || undefined, + } + this.client = new MongoClient(config.connectionString, options) } async connect() { diff --git a/packages/server/src/tests/utilities/structures.ts b/packages/server/src/tests/utilities/structures.ts index a412be4931..9d66fecc5e 100644 --- a/packages/server/src/tests/utilities/structures.ts +++ b/packages/server/src/tests/utilities/structures.ts @@ -1,8 +1,14 @@ -import { roles, permissions } from "@budibase/backend-core" +import { permissions, roles } from "@budibase/backend-core" import { createHomeScreen } from "../../constants/screens" import { EMPTY_LAYOUT } from "../../constants/layouts" import { cloneDeep } from "lodash/fp" -import { TRIGGER_DEFINITIONS, ACTION_DEFINITIONS } from "../../automations" +import { ACTION_DEFINITIONS, TRIGGER_DEFINITIONS } from "../../automations" +import { + Automation, + AutomationActionStepId, + AutomationTriggerStepId, +} from "@budibase/types" + const { v4: uuidv4 } = require("uuid") export const TENANT_ID = "default" @@ -116,6 +122,63 @@ export function basicAutomation() { } } +export function loopAutomation(tableId: string, loopOpts?: any): Automation { + if (!loopOpts) { + loopOpts = { + option: "Array", + binding: "{{ steps.1.rows }}", + } + } + const automation: any = { + name: "looping", + type: "automation", + definition: { + steps: [ + { + id: "b", + type: "ACTION", + stepId: AutomationActionStepId.QUERY_ROWS, + internal: true, + inputs: { + tableId, + }, + schema: ACTION_DEFINITIONS.QUERY_ROWS.schema, + }, + { + id: "c", + type: "ACTION", + stepId: AutomationActionStepId.LOOP, + internal: true, + inputs: loopOpts, + blockToLoop: "d", + schema: ACTION_DEFINITIONS.LOOP.schema, + }, + { + id: "d", + type: "ACTION", + internal: true, + stepId: AutomationActionStepId.SERVER_LOG, + inputs: { + text: "log statement", + }, + schema: ACTION_DEFINITIONS.SERVER_LOG.schema, + }, + ], + trigger: { + id: "a", + type: "TRIGGER", + event: "row:save", + stepId: AutomationTriggerStepId.ROW_SAVED, + inputs: { + tableId, + }, + schema: TRIGGER_DEFINITIONS.ROW_SAVED.schema, + }, + }, + } + return automation as Automation +} + export function basicRow(tableId: string) { return { name: "Test Contact", diff --git a/packages/server/src/threads/automation.ts b/packages/server/src/threads/automation.ts index 9355c89794..91105a2194 100644 --- a/packages/server/src/threads/automation.ts +++ b/packages/server/src/threads/automation.ts @@ -37,9 +37,13 @@ function getLoopIterations(loopStep: LoopStep, input: LoopInput) { if (!loopStep || !binding) { return 1 } - return Array.isArray(binding) - ? binding.length - : automationUtils.stringSplit(binding).length + if (Array.isArray(binding)) { + return binding.length + } + if (typeof binding === "string") { + return automationUtils.stringSplit(binding).length + } + return 1 } /** @@ -282,13 +286,13 @@ class Orchestrator { break } - let item + let item = [] if ( typeof loopStep.inputs.binding === "string" && loopStep.inputs.option === "String" ) { item = automationUtils.stringSplit(newInput.binding) - } else { + } else if (Array.isArray(loopStep.inputs.binding)) { item = loopStep.inputs.binding } this._context.steps[loopStepNumber] = { diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index bdf42a17e3..dba0d6328a 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -13,7 +13,8 @@ } }, "ts-node": { - "require": ["tsconfig-paths/register"] + "require": ["tsconfig-paths/register"], + "swc": true }, "references": [ { "path": "../types" }, diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index 2b670f6f90..c1647399d5 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -1273,13 +1273,13 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/backend-core@2.2.12-alpha.21": - version "2.2.12-alpha.21" - resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.21.tgz#99844f641cddb99ca6b6abd6a8af7d990e6b92e1" - integrity sha512-4ZFcLTRtApF1aCE2CJrgMO44O0rkQq9bmyBxXxMUMjmv83lsue8rpWV00G5Z7fCg2C/Z2VdzeOJU97OumT3dmQ== +"@budibase/backend-core@2.2.12-alpha.41": + version "2.2.12-alpha.41" + resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.41.tgz#9fa210c3c94481c38af5aad71ac246451dda6e43" + integrity sha512-vYb8x6JgncYdT8VqVi/WfScg4Ng0O1wtt9SspNPKnOX2CR7rA8VH6PW1QMRa5uUYvBFupg6UbY9jFqu2XXtujg== dependencies: "@budibase/nano" "10.1.1" - "@budibase/types" "2.2.12-alpha.21" + "@budibase/types" "2.2.12-alpha.41" "@shopify/jest-koa-mocks" "5.0.1" "@techpass/passport-openidconnect" "0.3.2" aws-cloudfront-sign "2.2.0" @@ -1374,13 +1374,13 @@ qs "^6.11.0" tough-cookie "^4.1.2" -"@budibase/pro@2.2.12-alpha.21": - version "2.2.12-alpha.21" - resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.21.tgz#5419fd78ac68ed9feddc4af52da8a79ff874cbfc" - integrity sha512-Fvm4ruWP9V514PJMqO9n2T4CQ2DGJpXDbK/hRSRooGyrAfR3lDJC6TB1boa4WYC+1YACF+f757adLPcNvUNCnA== +"@budibase/pro@2.2.12-alpha.41": + version "2.2.12-alpha.41" + resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.41.tgz#c1923d52d7cd2ace665e44b196c91578b1b50bbe" + integrity sha512-J1yN74Gixa8UzkD44Ydzj2iR+5WRbJtjZzn7NFI3VB1A2sTLxmilSBRyCALzhF3UMpueaBRjwWBovbF/De106A== dependencies: - "@budibase/backend-core" "2.2.12-alpha.21" - "@budibase/types" "2.2.12-alpha.21" + "@budibase/backend-core" "2.2.12-alpha.41" + "@budibase/types" "2.2.12-alpha.41" "@koa/router" "8.0.8" bull "4.10.1" joi "17.6.0" @@ -1405,10 +1405,10 @@ svelte-apexcharts "^1.0.2" svelte-flatpickr "^3.1.0" -"@budibase/types@2.2.12-alpha.21": - version "2.2.12-alpha.21" - resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.21.tgz#9344fae4504cf5d9a3a92ffb94434f988389c9a9" - integrity sha512-F+UMqKvrYqHYtJUcmvT9kRBFtPRPGBhbktJgYHa8D3X+CK/nf93UYN8j9nTeXme7iOA+i/cmg+zALFuHyZi45Q== +"@budibase/types@2.2.12-alpha.41": + version "2.2.12-alpha.41" + resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.41.tgz#662115c5ba09f3c2057a96321e233c819cfae84b" + integrity sha512-+uzr668cuvDTMqy7roWiG/qQzOzQO7uWYtysaHPsQQG5PxA0ZuwixJOvvX1qOr1rgv9Is54p9J7dvzvtKW/wAw== "@bull-board/api@3.7.0": version "3.7.0" @@ -5301,15 +5301,10 @@ color@^3.1.3: color-convert "^1.9.3" color-string "^1.6.0" -colorette@2.0.16: - version "2.0.16" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" - integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== - -colorette@^2.0.14: - version "2.0.17" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.17.tgz#5dd4c0d15e2984b7433cb4a9f2ead45063b80c47" - integrity sha512-hJo+3Bkn0NCHybn9Tu35fIeoOKGOk5OCC32y4Hz2It+qlCO2Q3DeQ1hRn/tDDMQKRYUEzqsl7jbF6dYKjlE60g== +colorette@2.0.19, colorette@^2.0.14: + version "2.0.19" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" + integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== colorspace@1.1.x: version "1.1.4" @@ -5346,7 +5341,7 @@ commander@^5.1.0: resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== -commander@^7.0.0, commander@^7.1.0: +commander@^7.0.0: version "7.2.0" resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== @@ -5356,6 +5351,11 @@ commander@^8.3.0: resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== +commander@^9.1.0: + version "9.5.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" + integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== + commoner@^0.10.1: version "0.10.8" resolved "https://registry.yarnpkg.com/commoner/-/commoner-0.10.8.tgz#34fc3672cd24393e8bb47e70caa0293811f4f2c5" @@ -5474,9 +5474,9 @@ cookie@^0.5.0: integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== cookiejar@^2.1.0: - version "2.1.3" - resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.3.tgz#fc7a6216e408e74414b90230050842dacda75acc" - integrity sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ== + version "2.1.4" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" + integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== cookies@~0.8.0: version "0.8.0" @@ -5659,20 +5659,13 @@ dayjs@^1.10.4, dayjs@^1.10.5: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.3.tgz#4754eb694a624057b9ad2224b67b15d552589258" integrity sha512-xxwlswWOlGhzgQ4TKzASQkUhqERI3egRNqgV4ScR8wlANA/A9tZ7miXa44vTTKEq5l7vWoL5G57bG3zA+Kow0A== -debug@4, debug@^4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: +debug@4, debug@4.3.4, debug@^4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" -debug@4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" - integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== - dependencies: - ms "2.1.2" - debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -7441,10 +7434,10 @@ get-value@^2.0.3, get-value@^2.0.6: resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA== -getopts@2.2.5: - version "2.2.5" - resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.2.5.tgz#67a0fe471cacb9c687d817cab6450b96dde8313b" - integrity sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA== +getopts@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.3.0.tgz#71e5593284807e03e2427449d4f6712a268666f4" + integrity sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA== getpass@^0.1.1: version "0.1.7" @@ -9825,23 +9818,24 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -knex@0.95.15: - version "0.95.15" - resolved "https://registry.yarnpkg.com/knex/-/knex-0.95.15.tgz#39d7e7110a6e2ad7de5d673d2dea94143015e0e7" - integrity sha512-Loq6WgHaWlmL2bfZGWPsy4l8xw4pOE+tmLGkPG0auBppxpI0UcK+GYCycJcqz9W54f2LiGewkCVLBm3Wq4ur/w== +knex@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/knex/-/knex-2.4.0.tgz#7d33cc36f320cdac98741010544b4c6a98b8b19e" + integrity sha512-i0GWwqYp1Hs2yvc2rlDO6nzzkLhwdyOZKRdsMTB8ZxOs2IXQyL5rBjSbS1krowCh6V65T4X9CJaKtuIfkaPGSA== dependencies: - colorette "2.0.16" - commander "^7.1.0" - debug "4.3.2" + colorette "2.0.19" + commander "^9.1.0" + debug "4.3.4" escalade "^3.1.1" esm "^3.2.25" - getopts "2.2.5" + get-package-type "^0.1.0" + getopts "2.3.0" interpret "^2.2.0" lodash "^4.17.21" pg-connection-string "2.5.0" - rechoir "0.7.0" + rechoir "^0.8.0" resolve-from "^5.0.0" - tarn "^3.0.1" + tarn "^3.0.2" tildify "2.0.0" koa-body@4.2.0: @@ -12561,13 +12555,6 @@ recast@^0.11.17: private "~0.1.5" source-map "~0.5.0" -rechoir@0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.0.tgz#32650fd52c21ab252aa5d65b19310441c7e03aca" - integrity sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q== - dependencies: - resolve "^1.9.0" - rechoir@^0.7.0: version "0.7.1" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.1.tgz#9478a96a1ca135b5e88fc027f03ee92d6c645686" @@ -12575,6 +12562,13 @@ rechoir@^0.7.0: dependencies: resolve "^1.9.0" +rechoir@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" + integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== + dependencies: + resolve "^1.20.0" + redis-commands@1.7.0, redis-commands@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89" @@ -14051,7 +14045,7 @@ tarn@^1.1.5: resolved "https://registry.yarnpkg.com/tarn/-/tarn-1.1.5.tgz#7be88622e951738b9fa3fb77477309242cdddc2d" integrity sha512-PMtJ3HCLAZeedWjJPgGnCvcphbCOMbtZpjKgLq3qM5Qq9aQud+XHrL0WlrlgnTyS8U+jrjGbEXprFcQrxPy52g== -tarn@^3.0.1: +tarn@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.2.tgz#73b6140fbb881b71559c4f8bfde3d9a4b3d27693" integrity sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ== diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 576900d69d..d70ebc28b2 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "2.2.12-alpha.21", + "version": "2.2.12-alpha.41", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.cjs", "module": "dist/bundle.mjs", diff --git a/packages/types/package.json b/packages/types/package.json index 4c5637e128..6d7c09d9a9 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/types", - "version": "2.2.12-alpha.21", + "version": "2.2.12-alpha.41", "description": "Budibase types", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/types/src/documents/app/automation.ts b/packages/types/src/documents/app/automation.ts index 184fed629d..0aa11d808b 100644 --- a/packages/types/src/documents/app/automation.ts +++ b/packages/types/src/documents/app/automation.ts @@ -50,6 +50,7 @@ export interface AutomationStepSchema { internal?: boolean deprecated?: boolean stepId: AutomationTriggerStepId | AutomationActionStepId + blockToLoop?: string inputs: { [key: string]: any } diff --git a/packages/types/src/sdk/datasources.ts b/packages/types/src/sdk/datasources.ts index 9d5b001a4b..605b431d9e 100644 --- a/packages/types/src/sdk/datasources.ts +++ b/packages/types/src/sdk/datasources.ts @@ -35,6 +35,7 @@ export enum DatasourceFieldType { OBJECT = "object", JSON = "json", FILE = "file", + FIELD_GROUP = "fieldGroup", } export enum SourceName { @@ -97,6 +98,16 @@ export interface ExtraQueryConfig { } } +export interface DatasourceConfig { + [key: string]: { + type: string + display?: string + required?: boolean + default?: any + deprecated?: boolean + } +} + export interface Integration { docs: string plus?: boolean @@ -106,16 +117,7 @@ export interface Integration { friendlyName: string type?: string iconUrl?: string - datasource: Record< - string, - { - type: string - display?: string - deprecated?: boolean - default?: any - required?: boolean - } - > + datasource: DatasourceConfig query: { [key: string]: QueryDefinition } diff --git a/packages/worker/package.json b/packages/worker/package.json index 1b4aedb664..83761f0a31 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/worker", "email": "hi@budibase.com", - "version": "2.2.12-alpha.21", + "version": "2.2.12-alpha.41", "description": "Budibase background service", "main": "src/index.ts", "repository": { @@ -36,10 +36,10 @@ "author": "Budibase", "license": "GPL-3.0", "dependencies": { - "@budibase/backend-core": "2.2.12-alpha.21", - "@budibase/pro": "2.2.12-alpha.21", - "@budibase/string-templates": "2.2.12-alpha.21", - "@budibase/types": "2.2.12-alpha.21", + "@budibase/backend-core": "2.2.12-alpha.41", + "@budibase/pro": "2.2.12-alpha.41", + "@budibase/string-templates": "2.2.12-alpha.41", + "@budibase/types": "2.2.12-alpha.41", "@koa/router": "8.0.8", "@sentry/node": "6.17.7", "@techpass/passport-openidconnect": "0.3.2", diff --git a/packages/worker/src/api/routes/global/configs.ts b/packages/worker/src/api/routes/global/configs.ts index 7420d749c2..38a31a28e6 100644 --- a/packages/worker/src/api/routes/global/configs.ts +++ b/packages/worker/src/api/routes/global/configs.ts @@ -34,8 +34,8 @@ function settingValidation() { function googleValidation() { // prettier-ignore return Joi.object({ - clientID: Joi.string().required(), - clientSecret: Joi.string().required(), + clientID: Joi.when('activated', { is: true, then: Joi.string().required() }), + clientSecret: Joi.when('activated', { is: true, then: Joi.string().required() }), activated: Joi.boolean().required(), }).unknown(true) } @@ -45,12 +45,12 @@ function oidcValidation() { return Joi.object({ configs: Joi.array().items( Joi.object({ - clientID: Joi.string().required(), - clientSecret: Joi.string().required(), - configUrl: Joi.string().required(), + clientID: Joi.when('activated', { is: true, then: Joi.string().required() }), + clientSecret: Joi.when('activated', { is: true, then: Joi.string().required() }), + configUrl: Joi.when('activated', { is: true, then: Joi.string().required() }), logo: Joi.string().allow("", null), name: Joi.string().allow("", null), - uuid: Joi.string().required(), + uuid: Joi.when('activated', { is: true, then: Joi.string().required() }), activated: Joi.boolean().required(), scopes: Joi.array().optional() }) diff --git a/packages/worker/tsconfig.json b/packages/worker/tsconfig.json index 3f6d4b30dc..3c8500d248 100644 --- a/packages/worker/tsconfig.json +++ b/packages/worker/tsconfig.json @@ -13,7 +13,8 @@ } }, "ts-node": { - "require": ["tsconfig-paths/register"] + "require": ["tsconfig-paths/register"], + "swc": true }, "references": [ { "path": "../types" }, diff --git a/packages/worker/yarn.lock b/packages/worker/yarn.lock index e38ee5dfff..f172c7f774 100644 --- a/packages/worker/yarn.lock +++ b/packages/worker/yarn.lock @@ -470,13 +470,13 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/backend-core@2.2.12-alpha.21": - version "2.2.12-alpha.21" - resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.21.tgz#99844f641cddb99ca6b6abd6a8af7d990e6b92e1" - integrity sha512-4ZFcLTRtApF1aCE2CJrgMO44O0rkQq9bmyBxXxMUMjmv83lsue8rpWV00G5Z7fCg2C/Z2VdzeOJU97OumT3dmQ== +"@budibase/backend-core@2.2.12-alpha.41": + version "2.2.12-alpha.41" + resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.41.tgz#9fa210c3c94481c38af5aad71ac246451dda6e43" + integrity sha512-vYb8x6JgncYdT8VqVi/WfScg4Ng0O1wtt9SspNPKnOX2CR7rA8VH6PW1QMRa5uUYvBFupg6UbY9jFqu2XXtujg== dependencies: "@budibase/nano" "10.1.1" - "@budibase/types" "2.2.12-alpha.21" + "@budibase/types" "2.2.12-alpha.41" "@shopify/jest-koa-mocks" "5.0.1" "@techpass/passport-openidconnect" "0.3.2" aws-cloudfront-sign "2.2.0" @@ -521,23 +521,23 @@ qs "^6.11.0" tough-cookie "^4.1.2" -"@budibase/pro@2.2.12-alpha.21": - version "2.2.12-alpha.21" - resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.21.tgz#5419fd78ac68ed9feddc4af52da8a79ff874cbfc" - integrity sha512-Fvm4ruWP9V514PJMqO9n2T4CQ2DGJpXDbK/hRSRooGyrAfR3lDJC6TB1boa4WYC+1YACF+f757adLPcNvUNCnA== +"@budibase/pro@2.2.12-alpha.41": + version "2.2.12-alpha.41" + resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.41.tgz#c1923d52d7cd2ace665e44b196c91578b1b50bbe" + integrity sha512-J1yN74Gixa8UzkD44Ydzj2iR+5WRbJtjZzn7NFI3VB1A2sTLxmilSBRyCALzhF3UMpueaBRjwWBovbF/De106A== dependencies: - "@budibase/backend-core" "2.2.12-alpha.21" - "@budibase/types" "2.2.12-alpha.21" + "@budibase/backend-core" "2.2.12-alpha.41" + "@budibase/types" "2.2.12-alpha.41" "@koa/router" "8.0.8" bull "4.10.1" joi "17.6.0" jsonwebtoken "8.5.1" node-fetch "^2.6.1" -"@budibase/types@2.2.12-alpha.21": - version "2.2.12-alpha.21" - resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.21.tgz#9344fae4504cf5d9a3a92ffb94434f988389c9a9" - integrity sha512-F+UMqKvrYqHYtJUcmvT9kRBFtPRPGBhbktJgYHa8D3X+CK/nf93UYN8j9nTeXme7iOA+i/cmg+zALFuHyZi45Q== +"@budibase/types@2.2.12-alpha.41": + version "2.2.12-alpha.41" + resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.41.tgz#662115c5ba09f3c2057a96321e233c819cfae84b" + integrity sha512-+uzr668cuvDTMqy7roWiG/qQzOzQO7uWYtysaHPsQQG5PxA0ZuwixJOvvX1qOr1rgv9Is54p9J7dvzvtKW/wAw== "@cspotcode/source-map-support@^0.8.0": version "0.8.1" @@ -2680,9 +2680,9 @@ cookie@^0.5.0: integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== cookiejar@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.3.tgz#fc7a6216e408e74414b90230050842dacda75acc" - integrity sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ== + version "2.1.4" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" + integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== cookies@~0.8.0: version "0.8.0" diff --git a/pull_request_template.md b/pull_request_template.md index ade39d55e4..36e2f425d5 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -11,5 +11,8 @@ Addresses: ## Screenshots _If a UI facing feature, a short video of the happy path, and some screenshots of the new functionality._ +## Documentation +- [ ] I have reviewed the budibase documentatation to verify if this feature requires any changes. If changes or new docs are required I have written them. +