Merge pull request #2842 from Budibase/develop

Develop -> master
This commit is contained in:
Martin McKeaveney 2021-10-01 16:32:32 +01:00 committed by GitHub
commit 2bebadcfb4
31 changed files with 249 additions and 936 deletions

View File

@ -50,6 +50,11 @@ static_resources:
route: route:
cluster: app-service cluster: app-service
- match: { path: "/api/deploy" }
route:
timeout: 60s
cluster: app-service
# special case for when API requests are made, can just forward, not to minio # special case for when API requests are made, can just forward, not to minio
- match: { prefix: "/api/" } - match: { prefix: "/api/" }
route: route:

View File

@ -1,5 +1,5 @@
{ {
"version": "0.9.148", "version": "0.9.149-alpha.3",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View File

@ -50,6 +50,8 @@
"multi:disable": "lerna run multi:disable", "multi:disable": "lerna run multi:disable",
"selfhost:enable": "lerna run selfhost:enable", "selfhost:enable": "lerna run selfhost:enable",
"selfhost:disable": "lerna run selfhost:disable", "selfhost:disable": "lerna run selfhost:disable",
"localdomain:enable": "lerna run localdomain:enable",
"localdomain:disable": "lerna run localdomain:disable",
"postinstall": "husky install" "postinstall": "husky install"
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/auth", "name": "@budibase/auth",
"version": "0.9.148", "version": "0.9.149-alpha.3",
"description": "Authentication middlewares for budibase builder and apps", "description": "Authentication middlewares for budibase builder and apps",
"main": "src/index.js", "main": "src/index.js",
"author": "Budibase", "author": "Budibase",

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/bbui", "name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.", "description": "A UI solution used in the different Budibase projects.",
"version": "0.9.148", "version": "0.9.149-alpha.3",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"module": "dist/bbui.es.js", "module": "dist/bbui.es.js",

View File

@ -90,6 +90,7 @@
on:input={onInput} on:input={onInput}
on:keyup={updateValueOnEnter} on:keyup={updateValueOnEnter}
{type} {type}
inputmode={type === "number" ? "decimal" : "text"}
class="spectrum-Textfield-input" class="spectrum-Textfield-input"
/> />
</div> </div>

View File

@ -24,9 +24,7 @@ context("Create a Table", () => {
it("updates a column on the table", () => { it("updates a column on the table", () => {
cy.get(".title").click() cy.get(".title").click()
cy.get(".spectrum-Table-editIcon > use").click() cy.get(".spectrum-Table-editIcon > use").click()
cy.get("input") cy.get("input").eq(1).type("updated", { force: true })
.eq(1)
.type("updated", { force: true })
// Unset table display column // Unset table display column
cy.get(".spectrum-Switch-input").eq(1).click() cy.get(".spectrum-Switch-input").eq(1).click()
cy.contains("Save Column").click() cy.contains("Save Column").click()
@ -45,9 +43,7 @@ context("Create a Table", () => {
it("deletes a row", () => { it("deletes a row", () => {
cy.get(".spectrum-Checkbox-input").check({ force: true }) cy.get(".spectrum-Checkbox-input").check({ force: true })
cy.contains("Delete 1 row(s)").click() cy.contains("Delete 1 row(s)").click()
cy.get(".spectrum-Modal") cy.get(".spectrum-Modal").contains("Delete").click()
.contains("Delete")
.click()
cy.contains("RoverUpdated").should("not.exist") cy.contains("RoverUpdated").should("not.exist")
}) })
@ -56,15 +52,18 @@ context("Create a Table", () => {
cy.get(".spectrum-Table-editIcon > use").click() cy.get(".spectrum-Table-editIcon > use").click()
cy.contains("Delete").click() cy.contains("Delete").click()
cy.wait(50) cy.wait(50)
cy.contains("Delete Column") cy.contains("Delete Column").click()
.click()
cy.contains("nameupdated").should("not.exist") cy.contains("nameupdated").should("not.exist")
}) })
it("deletes a table", () => { it("deletes a table", () => {
cy.get(".actions > :nth-child(1) > .icon > .spectrum-Icon > use") cy.get(".nav-item")
.eq(1) .contains("dog")
.click({ force: true }) .parents(".nav-item")
.first()
.within(() => {
cy.get(".actions .spectrum-Icon").click({ force: true })
})
cy.get(".spectrum-Menu > :nth-child(2)").click() cy.get(".spectrum-Menu > :nth-child(2)").click()
cy.contains("Delete Table").click() cy.contains("Delete Table").click()
cy.contains("dog").should("not.exist") cy.contains("dog").should("not.exist")

View File

@ -28,11 +28,7 @@ context("Create a View", () => {
const headers = Array.from($headers).map(header => const headers = Array.from($headers).map(header =>
header.textContent.trim() header.textContent.trim()
) )
expect(removeSpacing(headers)).to.deep.eq([ expect(removeSpacing(headers)).to.deep.eq(["group", "age", "rating"])
"group",
"age",
"rating",
])
}) })
}) })
@ -57,11 +53,12 @@ context("Create a View", () => {
}) })
it("creates a stats calculation view based on age", () => { it("creates a stats calculation view based on age", () => {
cy.wait(1000)
cy.contains("Calculate").click() cy.contains("Calculate").click()
cy.get(".modal-inner-wrapper").within(() => { cy.get(".modal-inner-wrapper").within(() => {
cy.get(".spectrum-Picker-label").eq(0).click() cy.get(".spectrum-Picker-label").eq(0).click()
cy.contains("Statistics").click() cy.contains("Statistics").click()
cy.get(".spectrum-Picker-label").eq(1).click() cy.get(".spectrum-Picker-label").eq(1).click()
cy.contains("age").click({ force: true }) cy.contains("age").click({ force: true })
@ -104,20 +101,20 @@ context("Create a View", () => {
cy.get(".spectrum-Table-cell").then($values => { cy.get(".spectrum-Table-cell").then($values => {
let values = Array.from($values).map(header => header.textContent.trim()) let values = Array.from($values).map(header => header.textContent.trim())
expect(values).to.deep.eq([ expect(values).to.deep.eq([
"Students", "Students",
"70", "70",
"20", "20",
"25", "25",
"3", "3",
"1650", "1650",
"23.333333333333332", "23.333333333333332",
"Teachers", "Teachers",
"85", "85",
"36", "36",
"49", "49",
"2", "2",
"3697", "3697",
"42.5", "42.5",
]) ])
}) })
}) })

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/builder", "name": "@budibase/builder",
"version": "0.9.148", "version": "0.9.149-alpha.3",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
@ -65,10 +65,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^0.9.148", "@budibase/bbui": "^0.9.149-alpha.3",
"@budibase/client": "^0.9.148", "@budibase/client": "^0.9.149-alpha.3",
"@budibase/colorpicker": "1.1.2", "@budibase/colorpicker": "1.1.2",
"@budibase/string-templates": "^0.9.148", "@budibase/string-templates": "^0.9.149-alpha.3",
"@sentry/browser": "5.19.1", "@sentry/browser": "5.19.1",
"@spectrum-css/page": "^3.0.1", "@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1", "@spectrum-css/vars": "^3.0.1",

View File

@ -1,12 +1,12 @@
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { get } from "svelte/store" import { get } from "svelte/store"
import { import {
findAllMatchingComponents,
findComponent, findComponent,
findComponentPath, findComponentPath,
findAllMatchingComponents,
} from "./storeUtils" } from "./storeUtils"
import { store } from "builderStore" import { store } from "builderStore"
import { tables as tablesStore, queries as queriesStores } from "stores/backend" import { queries as queriesStores, tables as tablesStore } from "stores/backend"
import { makePropSafe } from "@budibase/string-templates" import { makePropSafe } from "@budibase/string-templates"
import { TableNames } from "../constants" import { TableNames } from "../constants"
@ -422,6 +422,10 @@ function shouldReplaceBinding(currentValue, from, convertTo) {
return !invalids.find(invalid => noSpaces?.includes(invalid)) return !invalids.find(invalid => noSpaces?.includes(invalid))
} }
function replaceBetween(string, start, end, replacement) {
return string.substring(0, start) + replacement + string.substring(end)
}
/** /**
* utility function for the readableToRuntimeBinding and runtimeToReadableBinding. * utility function for the readableToRuntimeBinding and runtimeToReadableBinding.
*/ */
@ -431,6 +435,7 @@ function bindingReplacement(bindableProperties, textWithBindings, convertTo) {
if (typeof textWithBindings !== "string") { if (typeof textWithBindings !== "string") {
return textWithBindings return textWithBindings
} }
// work from longest to shortest
const convertFromProps = bindableProperties const convertFromProps = bindableProperties
.map(el => el[convertFrom]) .map(el => el[convertFrom])
.sort((a, b) => { .sort((a, b) => {
@ -440,12 +445,29 @@ function bindingReplacement(bindableProperties, textWithBindings, convertTo) {
let result = textWithBindings let result = textWithBindings
for (let boundValue of boundValues) { for (let boundValue of boundValues) {
let newBoundValue = boundValue let newBoundValue = boundValue
// we use a search string, where any time we replace something we blank it out
// in the search, working from longest to shortest so always use best match first
let searchString = newBoundValue
for (let from of convertFromProps) { for (let from of convertFromProps) {
if (shouldReplaceBinding(newBoundValue, from, convertTo)) { if (shouldReplaceBinding(newBoundValue, from, convertTo)) {
const binding = bindableProperties.find(el => el[convertFrom] === from) const binding = bindableProperties.find(el => el[convertFrom] === from)
while (newBoundValue.includes(from)) { let idx
newBoundValue = newBoundValue.replace(from, binding[convertTo]) do {
} // see if any instances of this binding exist in the search string
idx = searchString.indexOf(from)
if (idx !== -1) {
let end = idx + from.length,
searchReplace = Array(binding[convertTo].length).join("*")
// blank out parts of the search string
searchString = replaceBetween(searchString, idx, end, searchReplace)
newBoundValue = replaceBetween(
newBoundValue,
idx,
end,
binding[convertTo]
)
}
} while (idx !== -1)
} }
} }
result = result.replace(boundValue, newBoundValue) result = result.replace(boundValue, newBoundValue)

View File

@ -1,21 +1,10 @@
<script> <script>
import { onMount, onDestroy } from "svelte"
import { Button, Modal, notifications, ModalContent } from "@budibase/bbui" import { Button, Modal, notifications, ModalContent } from "@budibase/bbui"
import api from "builderStore/api" import api from "builderStore/api"
import analytics, { Events } from "analytics" import analytics, { Events } from "analytics"
import { store } from "builderStore" import { store } from "builderStore"
const DeploymentStatus = {
SUCCESS: "SUCCESS",
PENDING: "PENDING",
FAILURE: "FAILURE",
}
const POLL_INTERVAL = 10000
let feedbackModal let feedbackModal
let deployments = []
let poll
let publishModal let publishModal
async function deployApp() { async function deployApp() {
@ -34,62 +23,6 @@
notifications.error(`Error publishing app: ${err}`) notifications.error(`Error publishing app: ${err}`)
} }
} }
async function fetchDeployments() {
try {
const response = await api.get(`/api/deployments`)
const json = await response.json()
if (deployments.length > 0) {
checkIncomingDeploymentStatus(deployments, json)
}
deployments = json
} catch (err) {
console.error(err)
clearInterval(poll)
notifications.error(
"Error fetching deployment history. Please try again."
)
}
}
// Required to check any updated deployment statuses between polls
function checkIncomingDeploymentStatus(current, incoming) {
for (let incomingDeployment of incoming) {
if (
incomingDeployment.status === DeploymentStatus.FAILURE ||
incomingDeployment.status === DeploymentStatus.SUCCESS
) {
const currentDeployment = current.find(
deployment => deployment._id === incomingDeployment._id
)
// We have just been notified of an ongoing deployments status change
if (
!currentDeployment ||
currentDeployment.status === DeploymentStatus.PENDING
) {
if (incomingDeployment.status === DeploymentStatus.FAILURE) {
notifications.error(incomingDeployment.err)
} else {
notifications.send(
"Published to Production.",
"success",
"CheckmarkCircle"
)
}
}
}
}
}
onMount(() => {
fetchDeployments()
poll = setInterval(fetchDeployments, POLL_INTERVAL)
})
onDestroy(() => clearInterval(poll))
</script> </script>
<Button secondary on:click={publishModal.show}>Publish</Button> <Button secondary on:click={publishModal.show}>Publish</Button>

View File

@ -62,7 +62,7 @@
<Modal bind:this={modal}> <Modal bind:this={modal}>
<ModalContent <ModalContent
showConfirmButton={false} showConfirmButton={false}
cancelText="Close" cancelText="View changes"
showCloseIcon={false} showCloseIcon={false}
title="Theme settings" title="Theme settings"
> >

View File

@ -21,12 +21,12 @@
<ModalContent <ModalContent
size="M" size="M"
{onConfirm} {onConfirm}
title="Upgrade to self-hosted" title="Self-host Budibase"
confirmText="Upgrade" confirmText="Self-host Budibase"
> >
<span <span
>Upgrade to Budibase self-hosting for free, and get SSO, unlimited apps, >Self-host budibase for free, and get SSO, unlimited apps, and more - and
and more - and it only takes a few minutes!</span it only takes a few minutes!</span
> >
</ModalContent> </ModalContent>
</Modal> </Modal>

View File

@ -4,9 +4,6 @@
import { onMount } from "svelte" import { onMount } from "svelte"
let loaded = false let loaded = false
// don't react to these
let cloud = $admin.cloud
let shouldRedirect = !cloud || $admin.disableAccountPortal
$: multiTenancyEnabled = $admin.multiTenancy $: multiTenancyEnabled = $admin.multiTenancy
$: hasAdminUser = $admin?.checklist?.adminUser?.checked $: hasAdminUser = $admin?.checklist?.adminUser?.checked
@ -14,17 +11,33 @@
$: cloud = $admin.cloud $: cloud = $admin.cloud
$: user = $auth.user $: user = $auth.user
const validateTenantId = async () => { $: useAccountPortal = cloud && !$admin.disableAccountPortal
// set the tenant from the url in the cloud
const tenantId = window.location.host.split(".")[0]
if (!tenantId.includes("localhost:")) { const validateTenantId = async () => {
// user doesn't have permission to access this tenant - kick them out const host = window.location.host
if (user && user.tenantId !== tenantId) { if (host.includes("localhost:")) {
await auth.logout() // ignore local dev
await auth.setOrganisation(null) return
}
if (user && user.tenantId) {
let urlTenantId
const hostParts = host.split(".")
// only run validation when we know we are in a tenant url
// not when we visit the root budibase.app domain
// e.g. ['tenant', 'budibase', 'app'] vs ['budibase', 'app']
if (hostParts.length > 2) {
urlTenantId = hostParts[0]
} else { } else {
await auth.setOrganisation(tenantId) // no tenant in the url - send to account portal to fix this
window.location.href = $admin.accountPortalUrl
return
}
if (user.tenantId !== urlTenantId) {
// user should not be here - play it safe and log them out
await auth.logout()
} }
} }
} }
@ -33,7 +46,7 @@
await auth.checkAuth() await auth.checkAuth()
await admin.init() await admin.init()
if (cloud && multiTenancyEnabled) { if (useAccountPortal && multiTenancyEnabled) {
await validateTenantId() await validateTenantId()
} }
@ -41,12 +54,11 @@
}) })
$: { $: {
// We should never see the org or admin user creation screens in the cloud
const apiReady = $admin.loaded && $auth.loaded const apiReady = $admin.loaded && $auth.loaded
// if tenant is not set go to it // if tenant is not set go to it
if ( if (
loaded && loaded &&
shouldRedirect && !useAccountPortal &&
apiReady && apiReady &&
multiTenancyEnabled && multiTenancyEnabled &&
!tenantSet !tenantSet
@ -54,7 +66,7 @@
$redirect("./auth/org") $redirect("./auth/org")
} }
// Force creation of an admin user if one doesn't exist // Force creation of an admin user if one doesn't exist
else if (loaded && shouldRedirect && apiReady && !hasAdminUser) { else if (loaded && !useAccountPortal && apiReady && !hasAdminUser) {
$redirect("./admin") $redirect("./admin")
} }
// Redirect to log in at any time if the user isn't authenticated // Redirect to log in at any time if the user isn't authenticated

View File

@ -5,8 +5,11 @@
let loaded = false let loaded = false
$: cloud = $admin.cloud
$: useAccountPortal = cloud && !$admin.disableAccountPortal
onMount(() => { onMount(() => {
if ($admin?.checklist?.adminUser.checked) { if ($admin?.checklist?.adminUser.checked || useAccountPortal) {
$redirect("../") $redirect("../")
} else { } else {
loaded = true loaded = true

View File

@ -9,7 +9,8 @@
let tenantId = get(auth).tenantSet ? get(auth).tenantId : "" let tenantId = get(auth).tenantSet ? get(auth).tenantId : ""
$: multiTenancyEnabled = $admin.multiTenancy $: multiTenancyEnabled = $admin.multiTenancy
$: cloud = $admin.cloud $: cloud = $admin.cloud
$: disableAccountPortal = $admin.disableAccountPortal
$: useAccountPortal = cloud && !$admin.disableAccountPortal
async function setOrg() { async function setOrg() {
if (tenantId == null || tenantId === "") { if (tenantId == null || tenantId === "") {
@ -27,7 +28,7 @@
onMount(async () => { onMount(async () => {
await auth.checkQueryString() await auth.checkQueryString()
if (!multiTenancyEnabled || (cloud && !disableAccountPortal)) { if (!multiTenancyEnabled || useAccountPortal) {
$goto("../") $goto("../")
} else { } else {
admin.unload() admin.unload()

View File

@ -216,6 +216,7 @@
<div class="filter"> <div class="filter">
<div class="select"> <div class="select">
<Select <Select
autoWidth
bind:value={sortBy} bind:value={sortBy}
placeholder={null} placeholder={null}
options={[ options={[
@ -224,7 +225,9 @@
{ label: "Sort by status", value: "status" }, { label: "Sort by status", value: "status" },
]} ]}
/> />
<Search placeholder="Search" bind:value={searchTerm} /> <div class="desktop-search">
<Search placeholder="Search" bind:value={searchTerm} />
</div>
</div> </div>
<ActionGroup> <ActionGroup>
<ActionButton <ActionButton
@ -241,6 +244,9 @@
/> />
</ActionGroup> </ActionGroup>
</div> </div>
<div class="mobile-search">
<Search placeholder="Search" bind:value={searchTerm} />
</div>
<div <div
class:appGrid={layout === "grid"} class:appGrid={layout === "grid"}
class:appTable={layout === "table"} class:appTable={layout === "table"}
@ -318,6 +324,7 @@
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
gap: 10px;
} }
.select { .select {
@ -325,6 +332,12 @@
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
grid-gap: 10px; grid-gap: 10px;
} }
.filter :global(.spectrum-ActionGroup) {
flex-wrap: nowrap;
}
.mobile-search {
display: none;
}
.appGrid { .appGrid {
display: grid; display: grid;
@ -364,5 +377,11 @@
.appTable { .appTable {
grid-template-columns: 1fr auto; grid-template-columns: 1fr auto;
} }
.desktop-search {
display: none;
}
.mobile-search {
display: block;
}
} }
</style> </style>

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/cli", "name": "@budibase/cli",
"version": "0.9.148", "version": "0.9.149-alpha.3",
"description": "Budibase CLI, for developers, self hosting and migrations.", "description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js", "main": "src/index.js",
"bin": { "bin": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/client", "name": "@budibase/client",
"version": "0.9.148", "version": "0.9.149-alpha.3",
"license": "MPL-2.0", "license": "MPL-2.0",
"module": "dist/budibase-client.js", "module": "dist/budibase-client.js",
"main": "dist/budibase-client.js", "main": "dist/budibase-client.js",
@ -19,9 +19,9 @@
"dev:builder": "rollup -cw" "dev:builder": "rollup -cw"
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^0.9.148", "@budibase/bbui": "^0.9.149-alpha.3",
"@budibase/standard-components": "^0.9.139", "@budibase/standard-components": "^0.9.139",
"@budibase/string-templates": "^0.9.148", "@budibase/string-templates": "^0.9.149-alpha.3",
"regexparam": "^1.3.0", "regexparam": "^1.3.0",
"shortid": "^2.2.15", "shortid": "^2.2.15",
"svelte-spa-router": "^3.0.5" "svelte-spa-router": "^3.0.5"

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/server", "name": "@budibase/server",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "0.9.148", "version": "0.9.149-alpha.3",
"description": "Budibase Web Server", "description": "Budibase Web Server",
"main": "src/index.js", "main": "src/index.js",
"repository": { "repository": {
@ -64,9 +64,9 @@
"author": "Budibase", "author": "Budibase",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@budibase/auth": "^0.9.148", "@budibase/auth": "^0.9.149-alpha.3",
"@budibase/client": "^0.9.148", "@budibase/client": "^0.9.149-alpha.3",
"@budibase/string-templates": "^0.9.148", "@budibase/string-templates": "^0.9.149-alpha.3",
"@elastic/elasticsearch": "7.10.0", "@elastic/elasticsearch": "7.10.0",
"@koa/router": "8.0.0", "@koa/router": "8.0.0",
"@sendgrid/mail": "7.1.1", "@sendgrid/mail": "7.1.1",

View File

@ -7,14 +7,19 @@ if (env.POSTHOG_TOKEN && env.ENABLE_ANALYTICS && !env.SELF_HOSTED) {
posthogClient = new PostHog(env.POSTHOG_TOKEN) posthogClient = new PostHog(env.POSTHOG_TOKEN)
} }
exports.isEnabled = async function (ctx) { exports.isEnabled = async ctx => {
ctx.body = { ctx.body = {
enabled: !env.SELF_HOSTED && env.ENABLE_ANALYTICS === "true", enabled: !env.SELF_HOSTED && env.ENABLE_ANALYTICS === "true",
} }
} }
exports.endUserPing = async (ctx, next) => { exports.endUserPing = async ctx => {
if (!posthogClient) return next() if (!posthogClient) {
ctx.body = {
ping: false,
}
return
}
posthogClient.capture("budibase:end_user_ping", { posthogClient.capture("budibase:end_user_ping", {
userId: ctx.user && ctx.user._id, userId: ctx.user && ctx.user._id,

View File

@ -64,6 +64,7 @@ async function storeDeploymentHistory(deployment) {
async function initDeployedApp(prodAppId) { async function initDeployedApp(prodAppId) {
const db = new CouchDB(prodAppId) const db = new CouchDB(prodAppId)
console.log("Reading automation docs")
const automations = ( const automations = (
await db.allDocs( await db.allDocs(
getAutomationParams(null, { getAutomationParams(null, {
@ -71,12 +72,17 @@ async function initDeployedApp(prodAppId) {
}) })
) )
).rows.map(row => row.doc) ).rows.map(row => row.doc)
console.log("You have " + automations.length + " automations")
const promises = [] const promises = []
console.log("Disabling prod crons..")
await disableAllCrons(prodAppId) await disableAllCrons(prodAppId)
console.log("Prod Cron triggers disabled..")
console.log("Enabling cron triggers for deployed app..")
for (let automation of automations) { for (let automation of automations) {
promises.push(enableCronTrigger(prodAppId, automation)) promises.push(enableCronTrigger(prodAppId, automation))
} }
await Promise.all(promises) await Promise.all(promises)
console.log("Enabled cron triggers for deployed app..")
} }
async function deployApp(deployment) { async function deployApp(deployment) {
@ -88,13 +94,18 @@ async function deployApp(deployment) {
target: productionAppId, target: productionAppId,
}) })
console.log("Replication object created")
await replication.replicate() await replication.replicate()
console.log("replication complete.. replacing app meta doc")
const db = new CouchDB(productionAppId) const db = new CouchDB(productionAppId)
const appDoc = await db.get(DocumentTypes.APP_METADATA) const appDoc = await db.get(DocumentTypes.APP_METADATA)
appDoc.appId = productionAppId appDoc.appId = productionAppId
appDoc.instance._id = productionAppId appDoc.instance._id = productionAppId
await db.put(appDoc) await db.put(appDoc)
console.log("New app doc written successfully.")
console.log("Setting up live repl between dev and prod")
// Set up live sync between the live and dev instances // Set up live sync between the live and dev instances
const liveReplication = new Replication({ const liveReplication = new Replication({
source: productionAppId, source: productionAppId,
@ -105,8 +116,11 @@ async function deployApp(deployment) {
return doc._id !== DocumentTypes.APP_METADATA return doc._id !== DocumentTypes.APP_METADATA
}, },
}) })
console.log("Set up live repl between dev and prod")
console.log("Initialising deployed app")
await initDeployedApp(productionAppId) await initDeployedApp(productionAppId)
console.log("Init complete, setting deployment to successful")
deployment.setStatus(DeploymentStatus.SUCCESS) deployment.setStatus(DeploymentStatus.SUCCESS)
await storeDeploymentHistory(deployment) await storeDeploymentHistory(deployment)
} catch (err) { } catch (err) {
@ -153,9 +167,13 @@ exports.deploymentProgress = async function (ctx) {
exports.deployApp = async function (ctx) { exports.deployApp = async function (ctx) {
let deployment = new Deployment(ctx.appId) let deployment = new Deployment(ctx.appId)
console.log("Deployment object created")
deployment.setStatus(DeploymentStatus.PENDING) deployment.setStatus(DeploymentStatus.PENDING)
console.log("Deployment object set to pending")
deployment = await storeDeploymentHistory(deployment) deployment = await storeDeploymentHistory(deployment)
console.log("Stored deployment history")
console.log("Deploying app...")
await deployApp(deployment) await deployApp(deployment)
ctx.body = deployment ctx.body = deployment

View File

@ -403,16 +403,32 @@ exports.fetchEnrichedRow = async ctx => {
rowId, rowId,
}) })
// look up the actual rows based on the ids // look up the actual rows based on the ids
const response = await db.allDocs({ let response = (
include_docs: true, await db.allDocs({
keys: linkVals.map(linkVal => linkVal.id), include_docs: true,
}) keys: linkVals.map(linkVal => linkVal.id),
// need to include the IDs in these rows for any links they may have })
let linkedRows = await outputProcessing( ).rows.map(row => row.doc)
ctx, // group responses by table
table, let groups = {},
response.rows.map(row => row.doc) tables = {}
) for (let row of response) {
const linkedTableId = row.tableId
if (groups[linkedTableId] == null) {
groups[linkedTableId] = [row]
tables[linkedTableId] = await db.get(linkedTableId)
} else {
groups[linkedTableId].push(row)
}
}
let linkedRows = []
for (let [tableId, rows] of Object.entries(groups)) {
// need to include the IDs in these rows for any links they may have
linkedRows = linkedRows.concat(
await outputProcessing(ctx, tables[tableId], rows)
)
}
// insert the link rows in the correct place throughout the main row // insert the link rows in the correct place throughout the main row
for (let fieldName of Object.keys(table.schema)) { for (let fieldName of Object.keys(table.schema)) {
let field = table.schema[fieldName] let field = table.schema[fieldName]

View File

@ -3,7 +3,8 @@ const controller = require("../controllers/analytics")
const router = Router() const router = Router()
router.get("/api/analytics", controller.isEnabled) router
router.post("/api/analytics/ping", controller.endUserPing) .get("/api/analytics", controller.isEnabled)
.post("/api/analytics/ping", controller.endUserPing)
module.exports = router module.exports = router

View File

@ -99,6 +99,7 @@ function processAutoColumn(
row, row,
opts = { reprocessing: false, noAutoRelationships: false } opts = { reprocessing: false, noAutoRelationships: false }
) { ) {
let noUser = !user || !user.userId
let now = new Date().toISOString() let now = new Date().toISOString()
// if a row doesn't have a revision then it doesn't exist yet // if a row doesn't have a revision then it doesn't exist yet
const creating = !row._rev const creating = !row._rev
@ -108,7 +109,12 @@ function processAutoColumn(
} }
switch (schema.subtype) { switch (schema.subtype) {
case AutoFieldSubTypes.CREATED_BY: case AutoFieldSubTypes.CREATED_BY:
if (creating && !opts.reprocessing && !opts.noAutoRelationships) { if (
creating &&
!opts.reprocessing &&
!opts.noAutoRelationships &&
!noUser
) {
row[key] = [user.userId] row[key] = [user.userId]
} }
break break
@ -118,7 +124,7 @@ function processAutoColumn(
} }
break break
case AutoFieldSubTypes.UPDATED_BY: case AutoFieldSubTypes.UPDATED_BY:
if (!opts.reprocessing && !opts.noAutoRelationships) { if (!opts.reprocessing && !opts.noAutoRelationships && !noUser) {
row[key] = [user.userId] row[key] = [user.userId]
} }
break break

View File

@ -52,7 +52,8 @@ exports.sendSmtpEmail = async (to, from, subject, contents, automation) => {
) )
if (response.status !== 200) { if (response.status !== 200) {
throw "Unable to send email." const error = await response.text()
throw `Unable to send email - ${error}`
} }
return response.json() return response.json()
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/string-templates", "name": "@budibase/string-templates",
"version": "0.9.148", "version": "0.9.149-alpha.3",
"description": "Handlebars wrapper for Budibase templating.", "description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs", "main": "src/index.cjs",
"module": "dist/bundle.mjs", "module": "dist/bundle.mjs",

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/worker", "name": "@budibase/worker",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "0.9.148", "version": "0.9.149-alpha.3",
"description": "Budibase background service", "description": "Budibase background service",
"main": "src/index.js", "main": "src/index.js",
"repository": { "repository": {
@ -20,13 +20,15 @@
"multi:enable": "node scripts/multiTenancy.js enable", "multi:enable": "node scripts/multiTenancy.js enable",
"multi:disable": "node scripts/multiTenancy.js disable", "multi:disable": "node scripts/multiTenancy.js disable",
"selfhost:enable": "node scripts/selfhost.js enable", "selfhost:enable": "node scripts/selfhost.js enable",
"selfhost:disable": "node scripts/selfhost.js disable" "selfhost:disable": "node scripts/selfhost.js disable",
"localdomain:enable": "node scripts/localdomain.js enable",
"localdomain:disable": "node scripts/localdomain.js disable"
}, },
"author": "Budibase", "author": "Budibase",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@budibase/auth": "^0.9.148", "@budibase/auth": "^0.9.149-alpha.3",
"@budibase/string-templates": "^0.9.148", "@budibase/string-templates": "^0.9.149-alpha.3",
"@koa/router": "^8.0.0", "@koa/router": "^8.0.0",
"@techpass/passport-openidconnect": "^0.3.0", "@techpass/passport-openidconnect": "^0.3.0",
"aws-sdk": "^2.811.0", "aws-sdk": "^2.811.0",

View File

@ -0,0 +1,22 @@
#!/usr/bin/env node
const updateDotEnv = require("update-dotenv")
const arg = process.argv.slice(2)[0]
/**
* For testing multi tenancy sub domains locally.
*
* Relies on an entry in /etc/hosts e.g:
*
* 127.0.0.1 local.com
*
* and an entry for each tenant you wish to test locally e.g:
*
* 127.0.0.1 t1.local.com
* 127.0.0.1 t2.local.com
*/
updateDotEnv({
ACCOUNT_PORTAL_URL:
arg === "enable" ? "http://local.com:10001" : "http://localhost:10001",
COOKIE_DOMAIN: arg === "enable" ? ".local.com" : "",
}).then(() => console.log("Updated worker!"))

View File

@ -87,7 +87,7 @@ router
if (ctx.publicEndpoint) { if (ctx.publicEndpoint) {
return next() return next()
} }
if (!ctx.isAuthenticated || !ctx.user.budibaseAccess) { if ((!ctx.isAuthenticated || !ctx.user.budibaseAccess) && !ctx.internal) {
ctx.throw(403, "Unauthorized - no public worker access") ctx.throw(403, "Unauthorized - no public worker access")
} }
return next() return next()