Unpublish refactored to stop development applications being mistakenly deleted. Minor updates to the modal content component to allow the replacement of the header. Further work to implement the publishing workflow changes
This commit is contained in:
parent
c53c350879
commit
26c19891bb
|
@ -174,9 +174,11 @@ function getDB(key, opts) {
|
|||
if (db && isEqual(opts, storedOpts)) {
|
||||
return db
|
||||
}
|
||||
|
||||
const appId = exports.getAppId()
|
||||
const CouchDB = getCouch()
|
||||
let toUseAppId
|
||||
|
||||
switch (key) {
|
||||
case ContextKeys.CURRENT_DB:
|
||||
toUseAppId = appId
|
||||
|
|
|
@ -72,12 +72,20 @@
|
|||
class:header-spacing={$$slots.header}
|
||||
>
|
||||
{title}
|
||||
</h1>
|
||||
{:else if $$slots.header}
|
||||
<h1
|
||||
class="spectrum-Dialog-heading spectrum-Dialog-heading--noHeader"
|
||||
class:noDivider={!showDivider}
|
||||
class:header-spacing={$$slots.header}
|
||||
>
|
||||
<slot name="header" />
|
||||
</h1>
|
||||
{#if showDivider}
|
||||
<Divider size="M" />
|
||||
{/if}
|
||||
{/if}
|
||||
{#if showDivider && (title || $$slots.header)}
|
||||
<Divider size="M" />
|
||||
{/if}
|
||||
|
||||
<!-- TODO: Remove content-grid class once Layout components are in bbui -->
|
||||
<section class="spectrum-Dialog-content content-grid">
|
||||
<slot />
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
export let align = "right"
|
||||
export let portalTarget
|
||||
|
||||
let clazz
|
||||
export { clazz as class }
|
||||
|
||||
export const show = () => {
|
||||
dispatch("open")
|
||||
open = true
|
||||
|
@ -37,7 +40,7 @@
|
|||
use:positionDropdown={{ anchor, align }}
|
||||
use:clickOutside={hide}
|
||||
on:keydown={handleEscape}
|
||||
class="spectrum-Popover is-open"
|
||||
class={"spectrum-Popover is-open " + clazz}
|
||||
role="presentation"
|
||||
>
|
||||
<slot />
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
<script>
|
||||
import { setContext } from "svelte"
|
||||
import Popover from "../Popover/Popover.svelte"
|
||||
|
||||
export let disabled = false
|
||||
export let align = "left"
|
||||
|
||||
export let anchor
|
||||
export let showTip = true
|
||||
export let direction = "bottom"
|
||||
|
||||
let dropdown
|
||||
let tipSvg =
|
||||
'<svg xmlns="http://www.w3.org/svg/2000" width="23" height="12" class="spectrum-Popover-tip" > <path class="spectrum-Popover-tip-triangle" d="M 0.7071067811865476 0 L 11.414213562373096 10.707106781186548 L 22.121320343559645 0" /> </svg>'
|
||||
|
||||
// This is needed because display: contents is considered "invisible".
|
||||
// It should only ever be an action button, so should be fine.
|
||||
function getAnchor(node) {
|
||||
if (!anchor) {
|
||||
anchor = node.firstChild
|
||||
}
|
||||
}
|
||||
|
||||
//need this for the publish/view behaviours
|
||||
export const hide = () => {
|
||||
dropdown.hide()
|
||||
}
|
||||
export const show = () => {
|
||||
dropdown.show()
|
||||
}
|
||||
|
||||
const openMenu = event => {
|
||||
if (!disabled) {
|
||||
event.stopPropagation()
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
setContext("popoverMenu", { show, hide })
|
||||
</script>
|
||||
|
||||
<div class="popover-menu">
|
||||
<div use:getAnchor on:click={openMenu}>
|
||||
<slot name="control" />
|
||||
</div>
|
||||
<Popover
|
||||
bind:this={dropdown}
|
||||
{anchor}
|
||||
{align}
|
||||
class={showTip
|
||||
? `spectrum-Popover--withTip spectrum-Popover--${direction}`
|
||||
: ""}
|
||||
>
|
||||
{#if showTip}
|
||||
{@html tipSvg}
|
||||
{/if}
|
||||
|
||||
<div class="popover-container">
|
||||
<div class="popover-menu-wrap">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:global(.spectrum-Popover.is-open.spectrum-Popover--withTip) {
|
||||
margin-top: var(--spacing-xs);
|
||||
margin-left: var(--spacing-xl);
|
||||
}
|
||||
.popover-menu-wrap {
|
||||
padding: 10px;
|
||||
}
|
||||
.popover-menu :global(.icon) {
|
||||
display: flex;
|
||||
}
|
||||
:global(.spectrum-Popover--bottom .spectrum-Popover-tip) {
|
||||
left: 90%;
|
||||
margin-left: calc(var(--spectrum-global-dimension-size-150) * -1);
|
||||
}
|
||||
</style>
|
|
@ -25,6 +25,7 @@ export { default as RadioGroup } from "./Form/RadioGroup.svelte"
|
|||
export { default as Checkbox } from "./Form/Checkbox.svelte"
|
||||
export { default as DetailSummary } from "./DetailSummary/DetailSummary.svelte"
|
||||
export { default as Popover } from "./Popover/Popover.svelte"
|
||||
export { default as PopoverMenu } from "./Popover/PopoverMenu.svelte"
|
||||
export { default as ProgressBar } from "./ProgressBar/ProgressBar.svelte"
|
||||
export { default as ProgressCircle } from "./ProgressCircle/ProgressCircle.svelte"
|
||||
export { default as Label } from "./Label/Label.svelte"
|
||||
|
|
|
@ -10,11 +10,11 @@
|
|||
<ModalContent
|
||||
showCloseIcon={false}
|
||||
showConfirmButton={false}
|
||||
title="Test Automation"
|
||||
cancelText="Close"
|
||||
>
|
||||
<div slot="header">
|
||||
<div style="float: right;">
|
||||
<div slot="header" class="result-modal-header">
|
||||
<span>Test Automation</span>
|
||||
<div>
|
||||
{#if isTrigger || testResult[0].outputs.success}
|
||||
<div class="iconSuccess">
|
||||
<Icon size="S" name="CheckmarkCircle" />
|
||||
|
@ -89,6 +89,14 @@
|
|||
</ModalContent>
|
||||
|
||||
<style>
|
||||
.result-modal-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.iconSuccess {
|
||||
color: var(--spectrum-global-color-green-600);
|
||||
}
|
||||
|
|
|
@ -1,24 +1,57 @@
|
|||
<script>
|
||||
import { Button, Modal, notifications, ModalContent } from "@budibase/bbui"
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
notifications,
|
||||
ModalContent,
|
||||
Layout,
|
||||
} from "@budibase/bbui"
|
||||
import { API } from "api"
|
||||
import analytics, { Events } from "analytics"
|
||||
import { store } from "builderStore"
|
||||
import { ProgressCircle } from "@budibase/bbui"
|
||||
import CopyInput from "components/common/inputs/CopyInput.svelte"
|
||||
|
||||
let feedbackModal
|
||||
let publishModal
|
||||
let asyncModal
|
||||
let publishCompleteModal
|
||||
|
||||
let published
|
||||
|
||||
$: publishedUrl = published ? `${window.origin}/app${published.appUrl}` : ""
|
||||
|
||||
export let onOk
|
||||
|
||||
async function deployApp() {
|
||||
try {
|
||||
await API.deployAppChanges()
|
||||
published = await API.deployAppChanges()
|
||||
|
||||
//In Progress
|
||||
asyncModal.show()
|
||||
publishModal.hide()
|
||||
|
||||
analytics.captureEvent(Events.APP.PUBLISHED, {
|
||||
appId: $store.appId,
|
||||
})
|
||||
notifications.success("Application published successfully")
|
||||
if (typeof onOk === "function") {
|
||||
await onOk()
|
||||
}
|
||||
|
||||
//Request completed
|
||||
asyncModal.hide()
|
||||
publishCompleteModal.show()
|
||||
} catch (error) {
|
||||
analytics.captureException(error)
|
||||
notifications.error("Error publishing app")
|
||||
}
|
||||
}
|
||||
|
||||
const viewApp = () => {
|
||||
if (published) {
|
||||
window.open(publishedUrl, "_blank")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Button secondary on:click={publishModal.show}>Publish</Button>
|
||||
|
@ -30,6 +63,7 @@
|
|||
showCancelButton={false}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<Modal bind:this={publishModal}>
|
||||
<ModalContent
|
||||
title="Publish to Production"
|
||||
|
@ -42,3 +76,50 @@
|
|||
>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
<!-- Publish in progress -->
|
||||
<Modal bind:this={asyncModal}>
|
||||
<ModalContent
|
||||
showCancelButton={false}
|
||||
showConfirmButton={false}
|
||||
showCloseIcon={false}
|
||||
>
|
||||
<Layout justifyItems="center">
|
||||
<ProgressCircle size="XL" />
|
||||
</Layout>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
<!-- Publish complete -->
|
||||
<span class="publish-modal-wrap">
|
||||
<Modal bind:this={publishCompleteModal}>
|
||||
<ModalContent confirmText="Done" cancelText="View App" onCancel={viewApp}>
|
||||
<div slot="header" class="app-published-header">
|
||||
<svg
|
||||
width="26px"
|
||||
height="26px"
|
||||
class="spectrum-Icon success-icon"
|
||||
focusable="false"
|
||||
>
|
||||
<use xlink:href="#spectrum-icon-18-GlobeCheck" />
|
||||
</svg>
|
||||
<span class="app-published-header-text">App Published!</span>
|
||||
</div>
|
||||
<CopyInput value={publishedUrl} label="You can view your app at:" />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</span>
|
||||
|
||||
<style>
|
||||
.app-published-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
.success-icon {
|
||||
color: var(--spectrum-global-color-green-600);
|
||||
}
|
||||
.app-published-header .app-published-header-text {
|
||||
padding-left: var(--spacing-l);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -7,12 +7,10 @@
|
|||
import { notifications } from "@budibase/bbui"
|
||||
import CreateWebhookDeploymentModal from "./CreateWebhookDeploymentModal.svelte"
|
||||
import { store } from "builderStore"
|
||||
|
||||
const DeploymentStatus = {
|
||||
SUCCESS: "SUCCESS",
|
||||
PENDING: "PENDING",
|
||||
FAILURE: "FAILURE",
|
||||
}
|
||||
import {
|
||||
checkIncomingDeploymentStatus,
|
||||
DeploymentStatus,
|
||||
} from "components/deploy/utils"
|
||||
|
||||
const DATE_OPTIONS = {
|
||||
fullDate: {
|
||||
|
@ -42,30 +40,17 @@
|
|||
const formatDate = (date, format) =>
|
||||
Intl.DateTimeFormat("en-GB", DATE_OPTIONS[format]).format(date)
|
||||
|
||||
// Required to check any updated deployment statuses between polls
|
||||
function checkIncomingDeploymentStatus(current, incoming) {
|
||||
for (let incomingDeployment of incoming) {
|
||||
if (incomingDeployment.status === DeploymentStatus.FAILURE) {
|
||||
const currentDeployment = current.find(
|
||||
deployment => deployment._id === incomingDeployment._id
|
||||
)
|
||||
|
||||
// We have just been notified of an ongoing deployments failure
|
||||
if (
|
||||
!currentDeployment ||
|
||||
currentDeployment.status === DeploymentStatus.PENDING
|
||||
) {
|
||||
showErrorReasonModal(incomingDeployment.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchDeployments() {
|
||||
try {
|
||||
const newDeployments = await API.getAppDeployments()
|
||||
if (deployments.length > 0) {
|
||||
checkIncomingDeploymentStatus(deployments, newDeployments)
|
||||
const pendingDeployments = checkIncomingDeploymentStatus(
|
||||
deployments,
|
||||
newDeployments
|
||||
)
|
||||
if (pendingDeployments.length) {
|
||||
showErrorReasonModal(incomingDeployment.err)
|
||||
}
|
||||
}
|
||||
deployments = newDeployments
|
||||
} catch (err) {
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
export const DeploymentStatus = {
|
||||
SUCCESS: "SUCCESS",
|
||||
PENDING: "PENDING",
|
||||
FAILURE: "FAILURE",
|
||||
}
|
||||
|
||||
// Required to check any updated deployment statuses between polls
|
||||
export function checkIncomingDeploymentStatus(current, incoming) {
|
||||
return incoming.reduce((acc, incomingDeployment) => {
|
||||
if (incomingDeployment.status === DeploymentStatus.FAILURE) {
|
||||
const currentDeployment = current.find(
|
||||
deployment => deployment._id === incomingDeployment._id
|
||||
)
|
||||
|
||||
//We have just been notified of an ongoing deployments failure
|
||||
if (
|
||||
!currentDeployment ||
|
||||
currentDeployment.status === DeploymentStatus.PENDING
|
||||
) {
|
||||
acc.push(incomingDeployment)
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, [])
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
Icon,
|
||||
ActionMenu,
|
||||
MenuItem,
|
||||
ButtonGroup,
|
||||
StatusLight,
|
||||
} from "@budibase/bbui"
|
||||
import { processStringSync } from "@budibase/string-templates"
|
||||
|
@ -15,6 +16,7 @@
|
|||
export let editApp
|
||||
export let updateApp
|
||||
export let deleteApp
|
||||
export let previewApp
|
||||
export let unpublishApp
|
||||
export let releaseLock
|
||||
export let editIcon
|
||||
|
@ -57,19 +59,36 @@
|
|||
</StatusLight>
|
||||
</div>
|
||||
<div class="desktop">
|
||||
<StatusLight active={app.deployed} neutral={!app.deployed}>
|
||||
{#if app.deployed}Published{:else}Unpublished{/if}
|
||||
</StatusLight>
|
||||
<div class="app-status">
|
||||
{#if app.deployed}
|
||||
<Icon name="Globe" disabled={false} />
|
||||
Published
|
||||
{:else}
|
||||
<Icon name="GlobeStrike" disabled={true} />
|
||||
<span class="disabled"> Unpublished </span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div data-cy={`row_actions_${app.appId}`}>
|
||||
<Button
|
||||
size="S"
|
||||
disabled={app.lockedOther}
|
||||
on:click={() => editApp(app)}
|
||||
secondary
|
||||
>
|
||||
Open
|
||||
</Button>
|
||||
<div class="app-actions">
|
||||
{#if app.deployed}
|
||||
<Button size="S" secondary quiet on:click={() => viewApp(app)}
|
||||
>View app
|
||||
</Button>
|
||||
{:else}
|
||||
<Button size="S" secondary quiet on:click={() => previewApp(app)}
|
||||
>Preview
|
||||
</Button>
|
||||
{/if}
|
||||
<Button
|
||||
size="S"
|
||||
cta
|
||||
disabled={app.lockedOther}
|
||||
on:click={() => editApp(app)}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
<ActionMenu align="right">
|
||||
<Icon hoverable slot="control" name="More" />
|
||||
{#if app.deployed}
|
||||
|
@ -97,6 +116,18 @@
|
|||
</div>
|
||||
|
||||
<style>
|
||||
.app-actions {
|
||||
grid-gap: var(--spacing-s);
|
||||
display: grid;
|
||||
grid-template-columns: 75px 75px;
|
||||
}
|
||||
.app-status {
|
||||
display: grid;
|
||||
grid-template-columns: 24px 100px;
|
||||
}
|
||||
.app-status span.disabled {
|
||||
opacity: 0.3;
|
||||
}
|
||||
.name {
|
||||
text-decoration: none;
|
||||
overflow: hidden;
|
||||
|
|
|
@ -1,22 +1,69 @@
|
|||
<script>
|
||||
import { store, automationStore } from "builderStore"
|
||||
import { store, automationStore, allScreens } from "builderStore"
|
||||
import { roles, flags } from "stores/backend"
|
||||
import { Icon, ActionGroup, Tabs, Tab, notifications } from "@budibase/bbui"
|
||||
import {
|
||||
Icon,
|
||||
ActionGroup,
|
||||
Tabs,
|
||||
Tab,
|
||||
notifications,
|
||||
PopoverMenu,
|
||||
Layout,
|
||||
Button,
|
||||
Heading,
|
||||
} from "@budibase/bbui"
|
||||
import DeployModal from "components/deploy/DeployModal.svelte"
|
||||
import RevertModal from "components/deploy/RevertModal.svelte"
|
||||
import VersionModal from "components/deploy/VersionModal.svelte"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import { API } from "api"
|
||||
import { auth, admin } from "stores/portal"
|
||||
import { auth, admin, apps } from "stores/portal"
|
||||
import { isActive, goto, layout, redirect } from "@roxi/routify"
|
||||
import Logo from "assets/bb-emblem.svg"
|
||||
import { capitalise } from "helpers"
|
||||
import UpgradeModal from "components/upgrade/UpgradeModal.svelte"
|
||||
import { onMount, onDestroy } from "svelte"
|
||||
import { processStringSync } from "@budibase/string-templates"
|
||||
import { checkIncomingDeploymentStatus } from "components/deploy/utils"
|
||||
|
||||
export let application
|
||||
|
||||
// Get Package and set store
|
||||
let promise = getPackage()
|
||||
let unpublishModal
|
||||
let publishPopover
|
||||
let notPublishedPopover
|
||||
|
||||
$: enrichedApps = enrichApps($apps, $auth.user)
|
||||
const enrichApps = (apps, user) => {
|
||||
const enrichedApps = apps
|
||||
.map(app => ({
|
||||
...app,
|
||||
deployed: app.status === "published",
|
||||
lockedYou: app.lockedBy && app.lockedBy.email === user?.email,
|
||||
lockedOther: app.lockedBy && app.lockedBy.email !== user?.email,
|
||||
}))
|
||||
.filter(app => {
|
||||
return app.devId === application
|
||||
})
|
||||
|
||||
return enrichedApps
|
||||
}
|
||||
|
||||
$: selectedApp = enrichedApps.length > 0 ? enrichedApps[0] : {}
|
||||
|
||||
$: deployments = []
|
||||
$: latestDeployments = deployments
|
||||
.filter(deployment => deployment.status === "SUCCESS")
|
||||
.sort((a, b) => a.updatedAt > b.updatedAt)
|
||||
|
||||
$: console.log("Deployments ", deployments)
|
||||
$: console.log("Latest Deployments ", latestDeployments)
|
||||
|
||||
$: isPublished =
|
||||
selectedApp.deployed && latestDeployments && latestDeployments?.length
|
||||
? true
|
||||
: false
|
||||
|
||||
// Sync once when you load the app
|
||||
let hasSynced = false
|
||||
|
@ -24,10 +71,18 @@
|
|||
$layout.children.find(layout => $isActive(layout.path))?.title ?? "data"
|
||||
)
|
||||
|
||||
function previewApp() {
|
||||
const previewApp = () => {
|
||||
window.open(`/${application}`)
|
||||
}
|
||||
|
||||
const viewApp = () => {
|
||||
if (selectedApp.url) {
|
||||
window.open(`/app${selectedApp.url}`)
|
||||
} else {
|
||||
window.open(`/${selectedApp.prodId}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function getPackage() {
|
||||
try {
|
||||
const pkg = await API.fetchAppPackage(application)
|
||||
|
@ -58,20 +113,71 @@
|
|||
})
|
||||
}
|
||||
|
||||
const reviewPendingDeployments = (deployments, newDeployments) => {
|
||||
if (deployments.length > 0) {
|
||||
const pending = checkIncomingDeploymentStatus(deployments, newDeployments)
|
||||
if (pending.length) {
|
||||
notifications.warning(
|
||||
"Deployment has been queued and will be processed shortly"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchDeployments() {
|
||||
try {
|
||||
const newDeployments = await API.getAppDeployments()
|
||||
reviewPendingDeployments(deployments, newDeployments)
|
||||
return newDeployments
|
||||
} catch (err) {
|
||||
notifications.error("Error fetching deployment history")
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
if (!hasSynced && application) {
|
||||
try {
|
||||
await API.syncApp(application)
|
||||
await apps.load()
|
||||
} catch (error) {
|
||||
notifications.error("Failed to sync with production database")
|
||||
}
|
||||
hasSynced = true
|
||||
}
|
||||
deployments = await fetchDeployments()
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
store.actions.reset()
|
||||
})
|
||||
|
||||
const unpublishApp = () => {
|
||||
publishPopover.hide()
|
||||
unpublishModal.show()
|
||||
}
|
||||
|
||||
const completePublish = async () => {
|
||||
try {
|
||||
await apps.load()
|
||||
deployments = await fetchDeployments()
|
||||
} catch (err) {
|
||||
notifications.error("Error refreshing app")
|
||||
}
|
||||
}
|
||||
|
||||
const confirmUnpublishApp = async () => {
|
||||
if (!application || !isPublished) {
|
||||
//confirm the app has loaded.
|
||||
return
|
||||
}
|
||||
try {
|
||||
await API.unpublishApp(selectedApp.prodId)
|
||||
await apps.load()
|
||||
notifications.success("App unpublished successfully")
|
||||
} catch (err) {
|
||||
notifications.error("Error unpublishing app")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await promise}
|
||||
|
@ -112,23 +218,76 @@
|
|||
<VersionModal />
|
||||
<RevertModal />
|
||||
<Icon name="Play" hoverable on:click={previewApp} />
|
||||
<DeployModal />
|
||||
|
||||
<PopoverMenu
|
||||
bind:this={publishPopover}
|
||||
align="right"
|
||||
disabled={!isPublished}
|
||||
>
|
||||
<div slot="control" class="icon">
|
||||
<Icon
|
||||
size="M"
|
||||
hoverable
|
||||
name={isPublished ? "Globe" : "GlobeStrike"}
|
||||
disabled={!isPublished}
|
||||
/>
|
||||
</div>
|
||||
<Layout gap="M">
|
||||
<Heading size="XS">Your app is live!</Heading>
|
||||
<div class="publish-popover-message">
|
||||
{#if isPublished}
|
||||
{processStringSync(
|
||||
"Last Published: {{ duration time 'millisecond' }} ago",
|
||||
{
|
||||
time:
|
||||
new Date().getTime() -
|
||||
new Date(latestDeployments[0].updatedAt).getTime(),
|
||||
}
|
||||
)}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="publish-popover-actions">
|
||||
<Button
|
||||
warning={true}
|
||||
icon="Globe"
|
||||
disabled={!isPublished}
|
||||
on:click={unpublishApp}
|
||||
dataCy="publish-popover-action"
|
||||
>
|
||||
Unpublish
|
||||
</Button>
|
||||
<Button cta on:click={viewApp}>View App</Button>
|
||||
</div>
|
||||
</Layout>
|
||||
</PopoverMenu>
|
||||
|
||||
<DeployModal onOk={completePublish} />
|
||||
</div>
|
||||
</div>
|
||||
<slot />
|
||||
<ConfirmDialog
|
||||
bind:this={unpublishModal}
|
||||
title="Confirm unpublish"
|
||||
okText="Unpublish app"
|
||||
onOk={confirmUnpublishApp}
|
||||
>
|
||||
Are you sure you want to unpublish the app <b>{selectedApp?.name}</b>?
|
||||
</ConfirmDialog>
|
||||
</div>
|
||||
{:catch error}
|
||||
<p>Something went wrong: {error.message}</p>
|
||||
{/await}
|
||||
|
||||
<style>
|
||||
.publish-popover-actions :global([data-cy="publish-popover-action"]) {
|
||||
margin-right: var(--spacing-s);
|
||||
}
|
||||
.loading {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
.root {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
|
|
|
@ -174,6 +174,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
const previewApp = app => {
|
||||
window.open(`/${app.devId}`)
|
||||
}
|
||||
|
||||
const editApp = app => {
|
||||
if (app.lockedOther) {
|
||||
notifications.error(
|
||||
|
@ -392,6 +396,7 @@
|
|||
{exportApp}
|
||||
{deleteApp}
|
||||
{updateApp}
|
||||
{previewApp}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
@ -350,19 +350,25 @@ exports.revertClient = async ctx => {
|
|||
}
|
||||
|
||||
exports.delete = async ctx => {
|
||||
const db = getAppDB()
|
||||
let appId = ctx.params.appId
|
||||
|
||||
if (ctx.query.unpublish) {
|
||||
appId = getProdAppID(appId)
|
||||
}
|
||||
|
||||
const db = ctx.query.unpublish ? getProdAppDB() : getAppDB();
|
||||
const result = await db.destroy()
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!env.isTest() && !ctx.query.unpublish) {
|
||||
await deleteApp(ctx.params.appId)
|
||||
await deleteApp(appId)
|
||||
}
|
||||
if (ctx.query && ctx.query.unpublish) {
|
||||
await cleanupAutomations(ctx.params.appId)
|
||||
await cleanupAutomations(appId)
|
||||
}
|
||||
// make sure the app/role doesn't stick around after the app has been deleted
|
||||
await removeAppFromUserRoles(ctx, ctx.params.appId)
|
||||
await appCache.invalidateAppMetadata(ctx.params.appId)
|
||||
await removeAppFromUserRoles(ctx, appId)
|
||||
await appCache.invalidateAppMetadata(appId)
|
||||
|
||||
ctx.status = 200
|
||||
ctx.body = result
|
||||
|
|
|
@ -110,6 +110,9 @@ async function deployApp(deployment) {
|
|||
console.log("replication complete.. replacing app meta doc")
|
||||
const db = getProdAppDB()
|
||||
const appDoc = await db.get(DocumentTypes.APP_METADATA)
|
||||
|
||||
deployment.appUrl = appDoc.url
|
||||
|
||||
appDoc.appId = productionAppId
|
||||
appDoc.instance._id = productionAppId
|
||||
await db.put(appDoc)
|
||||
|
|
Loading…
Reference in New Issue