mirror of https://github.com/Budibase/budibase.git
32 changed files with 691 additions and 122 deletions
@ -0,0 +1,112 @@ |
|||
import filterTests from "../support/filterTests" |
|||
|
|||
filterTests(['all'], () => { |
|||
context("Publish Application Workflow", () => { |
|||
before(() => { |
|||
cy.login() |
|||
cy.createTestApp() |
|||
}) |
|||
|
|||
it("Should reflect the unpublished status correctly", () => { |
|||
cy.visit(`${Cypress.config().baseUrl}/builder`) |
|||
cy.wait(1000) |
|||
|
|||
cy.get(".appTable .app-status").eq(0) |
|||
.within(() => { |
|||
cy.contains("Unpublished") |
|||
cy.get("svg[aria-label='GlobeStrike']").should("exist") |
|||
}) |
|||
|
|||
cy.get(".appTable .app-row-actions").eq(0) |
|||
.within(() => { |
|||
cy.get(".spectrum-Button").contains("Preview") |
|||
cy.get(".spectrum-Button").contains("Edit").click({ force: true }) |
|||
}) |
|||
|
|||
cy.get(".deployment-top-nav svg[aria-label='GlobeStrike']").should("exist") |
|||
cy.get(".deployment-top-nav svg[aria-label='Globe']").should("not.exist") |
|||
}) |
|||
|
|||
it("Should publish an application and correctly reflect that", () => { |
|||
//Assuming the previous test was run and the unpublished app is open in edit mode.
|
|||
cy.get(".toprightnav button.spectrum-Button").contains("Publish").click({ force : true }) |
|||
|
|||
cy.get(".spectrum-Modal [data-cy='deploy-app-modal']").should("be.visible") |
|||
.within(() => { |
|||
cy.get(".spectrum-Button").contains("Publish").click({ force : true }) |
|||
cy.wait(1000) |
|||
}); |
|||
|
|||
//Verify that the app url is presented correctly to the user
|
|||
cy.get(".spectrum-Modal [data-cy='deploy-app-success-modal']") |
|||
.should("be.visible") |
|||
.within(() => { |
|||
let appUrl = Cypress.config().baseUrl + '/app/cypress-tests' |
|||
cy.get("[data-cy='deployed-app-url'] input").should('have.value', appUrl) |
|||
cy.get(".spectrum-Button").contains("Done").click({ force: true }) |
|||
}) |
|||
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder`) |
|||
cy.wait(1000) |
|||
|
|||
cy.get(".appTable .app-status").eq(0) |
|||
.within(() => { |
|||
cy.contains("Published") |
|||
cy.get("svg[aria-label='Globe']").should("exist") |
|||
}) |
|||
|
|||
cy.get(".appTable .app-row-actions").eq(0) |
|||
.within(() => { |
|||
cy.get(".spectrum-Button").contains("View app") |
|||
cy.get(".spectrum-Button").contains("Edit").click({ force: true }) |
|||
}) |
|||
|
|||
cy.get(".deployment-top-nav svg[aria-label='Globe']").should("exist").click({ force: true }) |
|||
|
|||
cy.get("[data-cy='publish-popover-menu']").should("be.visible") |
|||
.within(() => { |
|||
cy.get("[data-cy='publish-popover-action']").should("exist") |
|||
cy.get("button").contains("View app").should("exist") |
|||
cy.get(".publish-popover-message").should("have.text", "Last published a few seconds ago") |
|||
}) |
|||
}) |
|||
|
|||
it("Should unpublish an application from the top navigation and reflect the status change", () => { |
|||
//Assuming the previous test app exists and is published
|
|||
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder`) |
|||
|
|||
cy.get(".appTable .app-status").eq(0) |
|||
.within(() => { |
|||
cy.contains("Published") |
|||
cy.get("svg[aria-label='Globe']").should("exist") |
|||
}) |
|||
|
|||
cy.get(".appTable .app-row-actions").eq(0) |
|||
.within(() => { |
|||
cy.get(".spectrum-Button").contains("View app") |
|||
cy.get(".spectrum-Button").contains("Edit").click({ force: true }) |
|||
}) |
|||
|
|||
//The published status
|
|||
cy.get(".deployment-top-nav svg[aria-label='Globe']").should("exist") |
|||
.click({ force: true }) |
|||
|
|||
cy.get("[data-cy='publish-popover-menu']").should("be.visible") |
|||
cy.get("[data-cy='publish-popover-menu'] [data-cy='publish-popover-action']") |
|||
.click({ force : true }) |
|||
|
|||
cy.get("[data-cy='unpublish-modal']").should("be.visible") |
|||
.within(() => { |
|||
cy.get(".confirm-wrap button").click({ force: true } |
|||
)}) |
|||
|
|||
cy.get(".deployment-top-nav svg[aria-label='GlobeStrike']").should("exist") |
|||
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder`) |
|||
|
|||
cy.get(".appTable .app-status").eq(0).contains("Unpublished") |
|||
|
|||
}) |
|||
}) |
|||
}) |
|||
@ -0,0 +1,189 @@ |
|||
<script> |
|||
import { |
|||
notifications, |
|||
Popover, |
|||
Layout, |
|||
Heading, |
|||
Body, |
|||
Button, |
|||
Icon, |
|||
} from "@budibase/bbui" |
|||
import { processStringSync } from "@budibase/string-templates" |
|||
import ConfirmDialog from "components/common/ConfirmDialog.svelte" |
|||
import analytics, { Events, EventSource } from "analytics" |
|||
import { checkIncomingDeploymentStatus } from "components/deploy/utils" |
|||
import { API } from "api" |
|||
import { onMount } from "svelte" |
|||
import DeployModal from "components/deploy/DeployModal.svelte" |
|||
import { apps } from "stores/portal" |
|||
|
|||
export let application |
|||
|
|||
let publishPopover |
|||
let publishPopoverAnchor |
|||
let unpublishModal |
|||
|
|||
$: filteredApps = $apps.filter( |
|||
app => app.devId === application && app.status === "published" |
|||
) |
|||
$: selectedApp = filteredApps?.length ? filteredApps[0] : null |
|||
|
|||
$: deployments = [] |
|||
$: latestDeployments = deployments |
|||
.filter(deployment => deployment.status === "SUCCESS") |
|||
.sort((a, b) => a.updatedAt > b.updatedAt) |
|||
|
|||
$: isPublished = selectedApp && latestDeployments?.length > 0 |
|||
|
|||
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") |
|||
} |
|||
} |
|||
|
|||
const viewApp = () => { |
|||
analytics.captureEvent(Events.APP.VIEW_PUBLISHED, { |
|||
appId: selectedApp.appId, |
|||
eventSource: EventSource.PORTAL, |
|||
}) |
|||
if (selectedApp.url) { |
|||
window.open(`/app${selectedApp.url}`) |
|||
} else { |
|||
window.open(`/${selectedApp.prodId}`) |
|||
} |
|||
} |
|||
|
|||
const unpublishApp = () => { |
|||
publishPopover.hide() |
|||
unpublishModal.show() |
|||
} |
|||
|
|||
const confirmUnpublishApp = async () => { |
|||
if (!application || !isPublished) { |
|||
//confirm the app has loaded. |
|||
return |
|||
} |
|||
try { |
|||
analytics.captureEvent(Events.APP.UNPUBLISHED, { |
|||
appId: selectedApp.appId, |
|||
}) |
|||
await API.unpublishApp(selectedApp.prodId) |
|||
await apps.load() |
|||
notifications.success("App unpublished successfully") |
|||
} catch (err) { |
|||
notifications.error("Error unpublishing app") |
|||
} |
|||
} |
|||
|
|||
const completePublish = async () => { |
|||
try { |
|||
await apps.load() |
|||
deployments = await fetchDeployments() |
|||
} catch (err) { |
|||
notifications.error("Error refreshing app") |
|||
} |
|||
} |
|||
|
|||
onMount(async () => { |
|||
if (!$apps.length) { |
|||
await apps.load() |
|||
} |
|||
deployments = await fetchDeployments() |
|||
}) |
|||
</script> |
|||
|
|||
<div class="deployment-top-nav"> |
|||
{#if isPublished} |
|||
<div class="publish-popover"> |
|||
<div bind:this={publishPopoverAnchor}> |
|||
<Icon |
|||
size="M" |
|||
hoverable |
|||
name="Globe" |
|||
tooltip="Your published app" |
|||
on:click={publishPopover.show()} |
|||
/> |
|||
</div> |
|||
<Popover |
|||
bind:this={publishPopover} |
|||
align="right" |
|||
disabled={!isPublished} |
|||
dataCy="publish-popover-menu" |
|||
showTip={true} |
|||
anchor={publishPopoverAnchor} |
|||
> |
|||
<Layout gap="M"> |
|||
<Heading size="XS">Your published app</Heading> |
|||
<Body size="S"> |
|||
<span class="publish-popover-message"> |
|||
{processStringSync( |
|||
"Last published {{ duration time 'millisecond' }} ago", |
|||
{ |
|||
time: |
|||
new Date().getTime() - |
|||
new Date(latestDeployments[0].updatedAt).getTime(), |
|||
} |
|||
)} |
|||
</span> |
|||
</Body> |
|||
<div class="publish-popover-actions"> |
|||
<Button |
|||
warning={true} |
|||
icon="GlobeStrike" |
|||
disabled={!isPublished} |
|||
on:click={unpublishApp} |
|||
dataCy="publish-popover-action" |
|||
> |
|||
Unpublish |
|||
</Button> |
|||
<Button cta on:click={viewApp}>View app</Button> |
|||
</div> |
|||
</Layout> |
|||
</Popover> |
|||
</div> |
|||
{/if} |
|||
|
|||
{#if !isPublished} |
|||
<Icon |
|||
size="M" |
|||
name="GlobeStrike" |
|||
disabled |
|||
tooltip="Your app has not been published yet" |
|||
/> |
|||
{/if} |
|||
</div> |
|||
<ConfirmDialog |
|||
bind:this={unpublishModal} |
|||
title="Confirm unpublish" |
|||
okText="Unpublish app" |
|||
onOk={confirmUnpublishApp} |
|||
dataCy={"unpublish-modal"} |
|||
> |
|||
Are you sure you want to unpublish the app <b>{selectedApp?.name}</b>? |
|||
</ConfirmDialog> |
|||
|
|||
<DeployModal onOk={completePublish} /> |
|||
|
|||
<style> |
|||
.publish-popover-actions :global([data-cy="publish-popover-action"]) { |
|||
margin-right: var(--spacing-s); |
|||
} |
|||
:global([data-cy="publish-popover-menu"]) { |
|||
padding: 10px; |
|||
} |
|||
</style> |
|||
@ -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 |
|||
}, []) |
|||
} |
|||
Loading…
Reference in new issue