Merge branch 'feature/draft-apps' of github.com:Budibase/budibase into feature/draft-apps

This commit is contained in:
mike12345567 2021-05-13 18:10:20 +01:00
commit 332f0555a3
9 changed files with 70 additions and 59 deletions

View File

@ -11,47 +11,9 @@ class Replication {
this.target = getDB(target) this.target = getDB(target)
} }
sync(opts) { promisify(operation, opts = {}) {
return new Promise((resolve, reject) => { return new Promise(resolve => {
this.source operation(this.target, opts)
.sync(this.target, opts)
.on("change", function (info) {
// handle change
})
.on("paused", function (err) {
// replication paused (e.g. replication up to date, user went offline)
})
.on("active", function () {
// replicate resumed (e.g. new changes replicating, user went back online)
})
.on("denied", function (err) {
// a document failed to replicate (e.g. due to permissions)
return reject(
new Error(`Denied: Document failed to replicate ${err}`)
)
})
.on("complete", function (info) {
return resolve(info)
})
.on("error", function (err) {
return reject(new Error(`Replication Error: ${err}`))
})
})
}
replicate() {
return new Promise((resolve, reject) => {
this.replication = this.source.replicate
.to(this.target)
// .on("change", function (info) {
// // handle change
// })
// .on("paused", function (err) {
// // replication paused (e.g. replication up to date, user went offline)
// })
// .on("active", function () {
// // replicate resumed (e.g. new changes replicating, user went back online)
// })
.on("denied", function (err) { .on("denied", function (err) {
// a document failed to replicate (e.g. due to permissions) // a document failed to replicate (e.g. due to permissions)
throw new Error(`Denied: Document failed to replicate ${err}`) throw new Error(`Denied: Document failed to replicate ${err}`)
@ -65,6 +27,40 @@ class Replication {
}) })
} }
/**
* Two way replication operation, intended to be promise based.
* @param {Object} opts - PouchDB replication options
*/
sync(opts) {
this.replication = this.promisify(this.source.sync, opts)
return this.replication
}
/**
* One way replication operation, intended to be promise based.
* @param {Object} opts - PouchDB replication options
*/
replicate(opts) {
this.replication = this.promisify(this.source.replicate.to, opts)
return this.replication
}
/**
* Set up an ongoing live sync between 2 CouchDB databases.
* @param {Object} opts - PouchDB replication options
*/
subscribe(opts = {}) {
this.replication = this.source.replicate
.to(this.target, {
live: true,
retry: true,
...opts,
})
.on("error", function (err) {
throw new Error(`Replication Error: ${err}`)
})
}
async rollback() { async rollback() {
await this.target.destroy() await this.target.destroy()
await this.replicate() await this.replicate()

View File

@ -1,5 +1,5 @@
<script> <script>
import { General, DangerZone, APIKeys } from "./tabs" import { General, DangerZone } from "./tabs"
import { ModalContent, Tab, Tabs } from "@budibase/bbui" import { ModalContent, Tab, Tabs } from "@budibase/bbui"
</script> </script>
@ -13,9 +13,9 @@
<Tab title="General"> <Tab title="General">
<General /> <General />
</Tab> </Tab>
<Tab title="API Keys"> <!-- <Tab title="API Keys">
<APIKeys /> <APIKeys />
</Tab> </Tab> -->
<Tab title="Danger Zone"> <Tab title="Danger Zone">
<DangerZone /> <DangerZone />
</Tab> </Tab>

View File

@ -18,6 +18,7 @@
export let openApp export let openApp
export let deleteApp export let deleteApp
export let releaseLock export let releaseLock
export let deletable
</script> </script>
<div class="wrapper"> <div class="wrapper">
@ -34,9 +35,11 @@
<MenuItem on:click={() => exportApp(app)} icon="Download"> <MenuItem on:click={() => exportApp(app)} icon="Download">
Export Export
</MenuItem> </MenuItem>
<MenuItem on:click={() => deleteApp(app)} icon="Delete"> {#if deletable}
<MenuItem on:click={() => deleteApp(app)} icon="Delete">
Delete Delete
</MenuItem> </MenuItem>
{/if}
{#if app.lockedBy && app.lockedBy?.email === $auth.user?.email} {#if app.lockedBy && app.lockedBy?.email === $auth.user?.email}
<MenuItem on:click={() => releaseLock(app._id)} icon="LockOpen"> <MenuItem on:click={() => releaseLock(app._id)} icon="LockOpen">
Release Lock Release Lock

View File

@ -16,13 +16,14 @@
export let openApp export let openApp
export let exportApp export let exportApp
export let deleteApp export let deleteApp
export let last
export let releaseLock export let releaseLock
export let last
export let deletable
</script> </script>
<div class="title" class:last> <div class="title" class:last>
<div class="preview" use:gradient={{ seed: app.name }} /> <div class="preview" use:gradient={{ seed: app.name }} />
<Link on:click={openApp}> <Link on:click={() => openApp(app)}>
<Heading size="XS"> <Heading size="XS">
{app.name} {app.name}
</Heading> </Heading>
@ -45,7 +46,9 @@
<ActionMenu align="right"> <ActionMenu align="right">
<Icon hoverable slot="control" name="More" /> <Icon hoverable slot="control" name="More" />
<MenuItem on:click={() => exportApp(app)} icon="Download">Export</MenuItem> <MenuItem on:click={() => exportApp(app)} icon="Download">Export</MenuItem>
<MenuItem on:click={() => deleteApp(app)} icon="Delete">Delete</MenuItem> {#if deletable}
<MenuItem on:click={() => deleteApp(app)} icon="Delete">Delete</MenuItem>
{/if}
{#if app.lockedBy && app.lockedBy?.email === $auth.user?.email} {#if app.lockedBy && app.lockedBy?.email === $auth.user?.email}
<MenuItem on:click={() => releaseLock(app._id)} icon="LockOpen"> <MenuItem on:click={() => releaseLock(app._id)} icon="LockOpen">
Release Lock Release Lock

View File

@ -11,6 +11,7 @@ export const FrontendTypes = {
export const AppStatus = { export const AppStatus = {
DEV: "dev", DEV: "dev",
PUBLISHED: "published"
} }
// fields on the user table that cannot be edited // fields on the user table that cannot be edited

View File

@ -37,7 +37,7 @@
<img src={Rocket} alt="Rocket flying through sky" /> <img src={Rocket} alt="Rocket flying through sky" />
<div> <div>
<Heading size="M">It's time to shine!</Heading> <Heading size="M">It's time to shine!</Heading>
<Button size="XL" cta medium on:click={deployApp}>Deploy App</Button> <Button size="XL" cta medium on:click={deployApp}>Publish App</Button>
</div> </div>
</section> </section>
<Modal bind:this={feedbackModal}> <Modal bind:this={feedbackModal}>

View File

@ -26,7 +26,7 @@
import { AppStatus } from "constants" import { AppStatus } from "constants"
let layout = "grid" let layout = "grid"
let appStatus = "deployed" let appStatus = AppStatus.PUBLISHED
let template let template
let appToDelete let appToDelete
let creationModal let creationModal
@ -95,6 +95,7 @@
await del(`/api/applications/${appToDelete?._id}`) await del(`/api/applications/${appToDelete?._id}`)
await apps.load() await apps.load()
appToDelete = null appToDelete = null
notifications.success("App deleted successfully.")
} }
const releaseLock = async appId => { const releaseLock = async appId => {
@ -131,8 +132,8 @@
<Select <Select
bind:value={appStatus} bind:value={appStatus}
options={[ options={[
{ label: "Deployed", value: "deployed" }, { label: "Published", value: AppStatus.PUBLISHED },
{ label: "In Development", value: "dev" }, { label: "In Development", value: AppStatus.DEV },
]} ]}
/> />
</div> </div>
@ -159,6 +160,7 @@
{#each $apps as app, idx (app._id)} {#each $apps as app, idx (app._id)}
<svelte:component <svelte:component
this={layout === "grid" ? AppCard : AppRow} this={layout === "grid" ? AppCard : AppRow}
deletable={appStatus === AppStatus.PUBLISHED}
{releaseLock} {releaseLock}
{app} {app}
{openApp} {openApp}

View File

@ -57,24 +57,30 @@ async function storeLocalDeploymentHistory(deployment) {
async function deployApp(deployment) { async function deployApp(deployment) {
try { try {
const deployTarget = deployment.appId.replace("_dev", "") const productionAppId = deployment.appId.replace("_dev", "")
const replication = new Replication({ const replication = new Replication({
source: deployment.appId, source: deployment.appId,
target: deployTarget, target: productionAppId,
}) })
await replication.replicate() await replication.replicate()
// Strip the _dev prefix and update the appID document in the new DB // Strip the _dev prefix and update the appID document in the new DB
const db = new PouchDB(deployTarget) const db = new PouchDB(productionAppId)
const appDoc = await db.get(deployment.appId) const appDoc = await db.get(deployment.appId)
await db.remove(appDoc) appDoc._id = productionAppId
appDoc._id = deployTarget
delete appDoc._rev delete appDoc._rev
appDoc.instance._id = deployTarget appDoc.instance._id = productionAppId
await db.put(appDoc) await db.put(appDoc)
// Set up live sync between the live and dev instances
const liveReplication = new Replication({
source: productionAppId,
target: deployment.appId,
})
liveReplication.subscribe()
deployment.setStatus(DeploymentStatus.SUCCESS) deployment.setStatus(DeploymentStatus.SUCCESS)
await storeLocalDeploymentHistory(deployment) await storeLocalDeploymentHistory(deployment)
} catch (err) { } catch (err) {

View File

@ -17,7 +17,7 @@ const StaticDatabases = {
const AppStatus = { const AppStatus = {
DEV: "dev", DEV: "dev",
DEPLOYED: "deployed", DEPLOYED: "PUBLISHED",
} }
const DocumentTypes = { const DocumentTypes = {