dev mode E2E
This commit is contained in:
parent
2fa7ee5bcb
commit
61a5b109f5
|
@ -0,0 +1,78 @@
|
||||||
|
const { getDB } = require(".")
|
||||||
|
|
||||||
|
class Replication {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {String} source - the DB you want to replicate or rollback to
|
||||||
|
* @param {String} target - the DB you want to replicate to, or rollback from
|
||||||
|
*/
|
||||||
|
constructor({ source, target }) {
|
||||||
|
this.source = getDB(source)
|
||||||
|
this.target = getDB(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
sync(opts) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.source
|
||||||
|
.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) {
|
||||||
|
// a document failed to replicate (e.g. due to permissions)
|
||||||
|
throw new Error(`Denied: Document failed to replicate ${err}`)
|
||||||
|
})
|
||||||
|
.on("complete", function (info) {
|
||||||
|
return resolve(info)
|
||||||
|
})
|
||||||
|
.on("error", function (err) {
|
||||||
|
throw new Error(`Replication Error: ${err}`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async rollback() {
|
||||||
|
await this.target.destroy()
|
||||||
|
await this.replicate()
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.replication.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Replication
|
|
@ -1,4 +1,5 @@
|
||||||
const { newid } = require("../hashing")
|
const { newid } = require("../hashing")
|
||||||
|
const Replication = require("./Replication")
|
||||||
|
|
||||||
exports.ViewNames = {
|
exports.ViewNames = {
|
||||||
USER_BY_EMAIL: "by_email",
|
USER_BY_EMAIL: "by_email",
|
||||||
|
@ -165,6 +166,7 @@ async function getScopedConfig(db, params) {
|
||||||
return configDoc && configDoc.config ? configDoc.config : configDoc
|
return configDoc && configDoc.config ? configDoc.config : configDoc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.Replication = Replication
|
||||||
exports.getScopedConfig = getScopedConfig
|
exports.getScopedConfig = getScopedConfig
|
||||||
exports.generateConfigID = generateConfigID
|
exports.generateConfigID = generateConfigID
|
||||||
exports.getConfigParams = getConfigParams
|
exports.getConfigParams = getConfigParams
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<Link href={$url(`../../app/${app._id}`)}>
|
<Link href={$url(`../../app/${app._id}`)}>
|
||||||
<Heading size="XS">
|
<Heading size="XS">
|
||||||
{app.name}
|
{app._id} {app.name}
|
||||||
</Heading>
|
</Heading>
|
||||||
</Link>
|
</Link>
|
||||||
<ActionMenu align="right">
|
<ActionMenu align="right">
|
||||||
|
|
|
@ -15,11 +15,9 @@
|
||||||
$: appId = $store.appId
|
$: appId = $store.appId
|
||||||
|
|
||||||
async function deployApp() {
|
async function deployApp() {
|
||||||
const DEPLOY_URL = `/api/deploy`
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
notifications.info(`Deployment started. Please wait.`)
|
notifications.info(`Deployment started. Please wait.`)
|
||||||
const response = await api.post(DEPLOY_URL)
|
const response = await api.post("/api/deploy")
|
||||||
const json = await response.json()
|
const json = await response.json()
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
throw new Error()
|
throw new Error()
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
import AppRow from "components/start/AppRow.svelte"
|
import AppRow from "components/start/AppRow.svelte"
|
||||||
|
|
||||||
let layout = "grid"
|
let layout = "grid"
|
||||||
|
let appStatus = "deployed"
|
||||||
let template
|
let template
|
||||||
let appToDelete
|
let appToDelete
|
||||||
let creationModal
|
let creationModal
|
||||||
|
@ -32,6 +33,8 @@
|
||||||
let creatingApp = false
|
let creatingApp = false
|
||||||
let loaded = false
|
let loaded = false
|
||||||
|
|
||||||
|
$: appStatus && apps.load(appStatus)
|
||||||
|
|
||||||
const checkKeys = async () => {
|
const checkKeys = async () => {
|
||||||
const response = await api.get(`/api/keys/`)
|
const response = await api.get(`/api/keys/`)
|
||||||
const keys = await response.json()
|
const keys = await response.json()
|
||||||
|
@ -107,7 +110,13 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="filter">
|
<div class="filter">
|
||||||
<div class="select">
|
<div class="select">
|
||||||
<Select quiet placeholder="Filter by groups" />
|
<Select
|
||||||
|
bind:value={appStatus}
|
||||||
|
options={[
|
||||||
|
{ label: "Deployed", value: "deployed" },
|
||||||
|
{ label: "In Development", value: "dev" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ActionGroup>
|
<ActionGroup>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
|
|
|
@ -4,9 +4,9 @@ import { get } from "builderStore/api"
|
||||||
export function createAppStore() {
|
export function createAppStore() {
|
||||||
const store = writable([])
|
const store = writable([])
|
||||||
|
|
||||||
async function load() {
|
async function load(status = "") {
|
||||||
try {
|
try {
|
||||||
const res = await get("/api/applications")
|
const res = await get(`/api/applications?status=${status}`)
|
||||||
const json = await res.json()
|
const json = await res.json()
|
||||||
if (res.ok && Array.isArray(json)) {
|
if (res.ok && Array.isArray(json)) {
|
||||||
store.set(json)
|
store.set(json)
|
||||||
|
|
|
@ -2873,7 +2873,7 @@ svelte-spa-router@^3.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
regexparam "1.3.0"
|
regexparam "1.3.0"
|
||||||
|
|
||||||
svelte@^3.37.0:
|
svelte@^3.38.2:
|
||||||
version "3.38.2"
|
version "3.38.2"
|
||||||
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.38.2.tgz#55e5c681f793ae349b5cc2fe58e5782af4275ef5"
|
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.38.2.tgz#55e5c681f793ae349b5cc2fe58e5782af4275ef5"
|
||||||
integrity sha512-q5Dq0/QHh4BLJyEVWGe7Cej5NWs040LWjMbicBGZ+3qpFWJ1YObRmUDZKbbovddLC9WW7THTj3kYbTOFmU9fbg==
|
integrity sha512-q5Dq0/QHh4BLJyEVWGe7Cej5NWs040LWjMbicBGZ+3qpFWJ1YObRmUDZKbbovddLC9WW7THTj3kYbTOFmU9fbg==
|
||||||
|
|
|
@ -16,6 +16,9 @@ const {
|
||||||
getLayoutParams,
|
getLayoutParams,
|
||||||
getScreenParams,
|
getScreenParams,
|
||||||
generateScreenID,
|
generateScreenID,
|
||||||
|
generateDevAppID,
|
||||||
|
DocumentTypes,
|
||||||
|
AppStatus,
|
||||||
} = require("../../db/utils")
|
} = require("../../db/utils")
|
||||||
const {
|
const {
|
||||||
BUILTIN_ROLE_IDS,
|
BUILTIN_ROLE_IDS,
|
||||||
|
@ -84,7 +87,10 @@ async function getAppUrlIfNotInUse(ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createInstance(template) {
|
async function createInstance(template) {
|
||||||
const appId = generateAppID()
|
// TODO: Do we need the normal app ID?
|
||||||
|
const baseAppId = generateAppID()
|
||||||
|
const appId = generateDevAppID(baseAppId)
|
||||||
|
|
||||||
const db = new CouchDB(appId)
|
const db = new CouchDB(appId)
|
||||||
await db.put({
|
await db.put({
|
||||||
_id: "_design/database",
|
_id: "_design/database",
|
||||||
|
@ -114,7 +120,20 @@ async function createInstance(template) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetch = async function (ctx) {
|
exports.fetch = async function (ctx) {
|
||||||
ctx.body = await getAllApps()
|
let apps = await getAllApps()
|
||||||
|
|
||||||
|
const appStatus = ctx.query.status
|
||||||
|
if (appStatus) {
|
||||||
|
apps = apps.filter(app => {
|
||||||
|
if (appStatus === AppStatus.DEV) {
|
||||||
|
return app._id.startsWith(DocumentTypes.APP_DEV)
|
||||||
|
}
|
||||||
|
|
||||||
|
return !app._id.startsWith(DocumentTypes.APP_DEV)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.body = apps
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetchAppDefinition = async function (ctx) {
|
exports.fetchAppDefinition = async function (ctx) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const PouchDB = require("../../../db")
|
const PouchDB = require("../../../db")
|
||||||
const Deployment = require("./Deployment")
|
const Deployment = require("./Deployment")
|
||||||
|
const { Replication } = require("@budibase/auth").db
|
||||||
// the max time we can wait for an invalidation to complete before considering it failed
|
// the max time we can wait for an invalidation to complete before considering it failed
|
||||||
const MAX_PENDING_TIME_MS = 30 * 60000
|
const MAX_PENDING_TIME_MS = 30 * 60000
|
||||||
const DeploymentStatus = {
|
const DeploymentStatus = {
|
||||||
|
@ -56,7 +57,24 @@ async function storeLocalDeploymentHistory(deployment) {
|
||||||
|
|
||||||
async function deployApp(deployment) {
|
async function deployApp(deployment) {
|
||||||
try {
|
try {
|
||||||
// TODO: DB replication was here but wasn't accurate to new system
|
const deployTarget = deployment.appId.replace("_dev", "")
|
||||||
|
|
||||||
|
const replication = new Replication({
|
||||||
|
source: deployment.appId,
|
||||||
|
target: deployTarget,
|
||||||
|
})
|
||||||
|
|
||||||
|
await replication.replicate()
|
||||||
|
|
||||||
|
// Strip the _dev prefix and update the appID document in the new DB
|
||||||
|
const db = new PouchDB(deployTarget)
|
||||||
|
const appDoc = await db.get(deployment.appId)
|
||||||
|
await db.remove(appDoc)
|
||||||
|
appDoc._id = deployTarget
|
||||||
|
delete appDoc._rev
|
||||||
|
appDoc.instance._id = deployTarget
|
||||||
|
await db.put(appDoc)
|
||||||
|
|
||||||
deployment.setStatus(DeploymentStatus.SUCCESS)
|
deployment.setStatus(DeploymentStatus.SUCCESS)
|
||||||
await storeLocalDeploymentHistory(deployment)
|
await storeLocalDeploymentHistory(deployment)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -37,7 +37,7 @@ router
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.use(currentApp)
|
.use(currentApp)
|
||||||
.use(development)
|
// .use(development)
|
||||||
|
|
||||||
// error handling middleware
|
// error handling middleware
|
||||||
router.use(async (ctx, next) => {
|
router.use(async (ctx, next) => {
|
||||||
|
|
|
@ -10,6 +10,11 @@ const StaticDatabases = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AppStatus = {
|
||||||
|
DEV: "dev",
|
||||||
|
DEPLOYED: "deployed",
|
||||||
|
}
|
||||||
|
|
||||||
const DocumentTypes = {
|
const DocumentTypes = {
|
||||||
TABLE: "ta",
|
TABLE: "ta",
|
||||||
ROW: "ro",
|
ROW: "ro",
|
||||||
|
@ -49,6 +54,7 @@ exports.DocumentTypes = DocumentTypes
|
||||||
exports.SEPARATOR = SEPARATOR
|
exports.SEPARATOR = SEPARATOR
|
||||||
exports.UNICODE_MAX = UNICODE_MAX
|
exports.UNICODE_MAX = UNICODE_MAX
|
||||||
exports.SearchIndexes = SearchIndexes
|
exports.SearchIndexes = SearchIndexes
|
||||||
|
exports.AppStatus = AppStatus
|
||||||
|
|
||||||
exports.getQueryIndex = viewName => {
|
exports.getQueryIndex = viewName => {
|
||||||
return `database/${viewName}`
|
return `database/${viewName}`
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -226,7 +226,7 @@ svelte-hmr@^0.13.3:
|
||||||
resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.13.3.tgz#fba5739b477ea44caf70e542a24a4352bee2b897"
|
resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.13.3.tgz#fba5739b477ea44caf70e542a24a4352bee2b897"
|
||||||
integrity sha512-gagW62pLQ2lULmvNA3pIZu9pBCYOaGu3rQikUOv6Nokz5VxUgT9/mQLfMxj9phDEKHCg/lgr3i6PkqZDbO9P2Q==
|
integrity sha512-gagW62pLQ2lULmvNA3pIZu9pBCYOaGu3rQikUOv6Nokz5VxUgT9/mQLfMxj9phDEKHCg/lgr3i6PkqZDbO9P2Q==
|
||||||
|
|
||||||
svelte@^3.37.0:
|
svelte@^3.38.2:
|
||||||
version "3.38.2"
|
version "3.38.2"
|
||||||
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.38.2.tgz#55e5c681f793ae349b5cc2fe58e5782af4275ef5"
|
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.38.2.tgz#55e5c681f793ae349b5cc2fe58e5782af4275ef5"
|
||||||
integrity sha512-q5Dq0/QHh4BLJyEVWGe7Cej5NWs040LWjMbicBGZ+3qpFWJ1YObRmUDZKbbovddLC9WW7THTj3kYbTOFmU9fbg==
|
integrity sha512-q5Dq0/QHh4BLJyEVWGe7Cej5NWs040LWjMbicBGZ+3qpFWJ1YObRmUDZKbbovddLC9WW7THTj3kYbTOFmU9fbg==
|
||||||
|
|
Loading…
Reference in New Issue