Test fixes and code review refactoring
This commit is contained in:
parent
b3db4257f2
commit
24a9656cbc
|
@ -67,19 +67,21 @@
|
||||||
data-cy={dataCy}
|
data-cy={dataCy}
|
||||||
>
|
>
|
||||||
<div class="spectrum-Dialog-grid">
|
<div class="spectrum-Dialog-grid">
|
||||||
<h1
|
{#if title || $$slots.header}
|
||||||
class="spectrum-Dialog-heading spectrum-Dialog-heading--noHeader"
|
<h1
|
||||||
class:noDivider={!showDivider}
|
class="spectrum-Dialog-heading spectrum-Dialog-heading--noHeader"
|
||||||
class:header-spacing={$$slots.header}
|
class:noDivider={!showDivider}
|
||||||
>
|
class:header-spacing={$$slots.header}
|
||||||
{#if title}
|
>
|
||||||
{title}
|
{#if title}
|
||||||
{:else if $$slots.header}
|
{title}
|
||||||
<slot name="header" />
|
{:else if $$slots.header}
|
||||||
|
<slot name="header" />
|
||||||
|
{/if}
|
||||||
|
</h1>
|
||||||
|
{#if showDivider}
|
||||||
|
<Divider size="M" />
|
||||||
{/if}
|
{/if}
|
||||||
</h1>
|
|
||||||
{#if showDivider && (title || $$slots.header)}
|
|
||||||
<Divider size="M" />
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- TODO: Remove content-grid class once Layout components are in bbui -->
|
<!-- TODO: Remove content-grid class once Layout components are in bbui -->
|
||||||
|
|
|
@ -12,8 +12,15 @@
|
||||||
export let portalTarget
|
export let portalTarget
|
||||||
export let dataCy
|
export let dataCy
|
||||||
|
|
||||||
let clazz
|
export let direction = "bottom"
|
||||||
export { clazz as class }
|
export let showTip = false
|
||||||
|
|
||||||
|
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>'
|
||||||
|
|
||||||
|
$: tooltipClasses = showTip
|
||||||
|
? `spectrum-Popover--withTip spectrum-Popover--${direction}`
|
||||||
|
: ""
|
||||||
|
|
||||||
export const show = () => {
|
export const show = () => {
|
||||||
dispatch("open")
|
dispatch("open")
|
||||||
|
@ -41,10 +48,14 @@
|
||||||
use:positionDropdown={{ anchor, align }}
|
use:positionDropdown={{ anchor, align }}
|
||||||
use:clickOutside={hide}
|
use:clickOutside={hide}
|
||||||
on:keydown={handleEscape}
|
on:keydown={handleEscape}
|
||||||
class={"spectrum-Popover is-open " + (clazz || "")}
|
class={"spectrum-Popover is-open " + (tooltipClasses || "")}
|
||||||
role="presentation"
|
role="presentation"
|
||||||
data-cy={dataCy}
|
data-cy={dataCy}
|
||||||
>
|
>
|
||||||
|
{#if showTip}
|
||||||
|
{@html tipSvg}
|
||||||
|
{/if}
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</Portal>
|
</Portal>
|
||||||
|
@ -54,4 +65,13 @@
|
||||||
.spectrum-Popover {
|
.spectrum-Popover {
|
||||||
min-width: var(--spectrum-global-dimension-size-2000) !important;
|
min-width: var(--spectrum-global-dimension-size-2000) !important;
|
||||||
}
|
}
|
||||||
|
.spectrum-Popover.is-open.spectrum-Popover--withTip {
|
||||||
|
margin-top: var(--spacing-xs);
|
||||||
|
margin-left: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
:global(.spectrum-Popover--bottom .spectrum-Popover-tip),
|
||||||
|
:global(.spectrum-Popover--top .spectrum-Popover-tip) {
|
||||||
|
left: 90%;
|
||||||
|
margin-left: calc(var(--spectrum-global-dimension-size-150) * -1);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
<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"
|
|
||||||
export let dataCy = null
|
|
||||||
|
|
||||||
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" data-cy={dataCy}>
|
|
||||||
<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,7 +25,6 @@ export { default as RadioGroup } from "./Form/RadioGroup.svelte"
|
||||||
export { default as Checkbox } from "./Form/Checkbox.svelte"
|
export { default as Checkbox } from "./Form/Checkbox.svelte"
|
||||||
export { default as DetailSummary } from "./DetailSummary/DetailSummary.svelte"
|
export { default as DetailSummary } from "./DetailSummary/DetailSummary.svelte"
|
||||||
export { default as Popover } from "./Popover/Popover.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 ProgressBar } from "./ProgressBar/ProgressBar.svelte"
|
||||||
export { default as ProgressCircle } from "./ProgressCircle/ProgressCircle.svelte"
|
export { default as ProgressCircle } from "./ProgressCircle/ProgressCircle.svelte"
|
||||||
export { default as Label } from "./Label/Label.svelte"
|
export { default as Label } from "./Label/Label.svelte"
|
||||||
|
|
|
@ -23,8 +23,8 @@ filterTests(['all'], () => {
|
||||||
cy.get(".spectrum-Button").contains("Edit").click({ force: true })
|
cy.get(".spectrum-Button").contains("Edit").click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.get(".app-status-icon svg[aria-label='GlobeStrike']").should("exist")
|
cy.get(".deployment-top-nav svg[aria-label='GlobeStrike']").should("exist")
|
||||||
cy.get(".app-status-icon svg[aria-label='Globe']").should("not.exist")
|
cy.get(".deployment-top-nav svg[aria-label='Globe']").should("not.exist")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Should publish an application and correctly reflect that", () => {
|
it("Should publish an application and correctly reflect that", () => {
|
||||||
|
@ -61,13 +61,13 @@ filterTests(['all'], () => {
|
||||||
cy.get(".spectrum-Button").contains("Edit").click({ force: true })
|
cy.get(".spectrum-Button").contains("Edit").click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.get(".app-status-icon svg[aria-label='Globe']").should("exist").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")
|
cy.get("[data-cy='publish-popover-menu']").should("be.visible")
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get("[data-cy='publish-popover-action']").should("exist")
|
cy.get("[data-cy='publish-popover-action']").should("exist")
|
||||||
cy.get("button").contains("View App").should("exist")
|
cy.get("button").contains("View app").should("exist")
|
||||||
cy.get(".publish-popover-message").should("have.text", "Last Published: a few seconds ago")
|
cy.get(".publish-popover-message").should("have.text", "Last published a few seconds ago")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ filterTests(['all'], () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
//The published status
|
//The published status
|
||||||
cy.get(".app-status-icon svg[aria-label='Globe']").should("exist")
|
cy.get(".deployment-top-nav svg[aria-label='Globe']").should("exist")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
|
|
||||||
cy.get("[data-cy='publish-popover-menu']").should("be.visible")
|
cy.get("[data-cy='publish-popover-menu']").should("be.visible")
|
||||||
|
@ -101,7 +101,7 @@ filterTests(['all'], () => {
|
||||||
cy.get(".confirm-wrap button").click({ force: true }
|
cy.get(".confirm-wrap button").click({ force: true }
|
||||||
)})
|
)})
|
||||||
|
|
||||||
cy.get(".app-status-icon svg[aria-label='GlobeStrike']").should("exist")
|
cy.get(".deployment-top-nav svg[aria-label='GlobeStrike']").should("exist")
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
|
||||||
|
|
|
@ -1,51 +1,51 @@
|
||||||
import filterTests from "../support/filterTests"
|
import filterTests from "../support/filterTests"
|
||||||
|
|
||||||
filterTests(['smoke', 'all'], () => {
|
filterTests(['smoke', 'all'], () => {
|
||||||
context("Auto Screens UI", () => {
|
context("Auto Screens UI", () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.login()
|
cy.login()
|
||||||
cy.createTestApp()
|
cy.createTestApp()
|
||||||
})
|
|
||||||
|
|
||||||
it("should generate internal table screens", () => {
|
|
||||||
// Create autogenerated screens from the internal table
|
|
||||||
cy.createAutogeneratedScreens(["Cypress Tests"])
|
|
||||||
// Confirm screens have been auto generated
|
|
||||||
cy.get(".nav-items-container").contains("cypress-tests").click({ force: true })
|
|
||||||
cy.get(".nav-items-container").should('contain', 'cypress-tests/:id')
|
|
||||||
.and('contain', 'cypress-tests/new/row')
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should generate multiple internal table screens at once", () => {
|
|
||||||
// Create a second internal table
|
|
||||||
const initialTable = "Cypress Tests"
|
|
||||||
const secondTable = "Table Two"
|
|
||||||
cy.createTable(secondTable)
|
|
||||||
// Create autogenerated screens from the internal tables
|
|
||||||
cy.createAutogeneratedScreens([initialTable, secondTable])
|
|
||||||
// Confirm screens have been auto generated
|
|
||||||
cy.get(".nav-items-container").contains("cypress-tests").click({ force: true })
|
|
||||||
// Previously generated tables are suffixed with numbers - as expected
|
|
||||||
cy.get(".nav-items-container").should('contain', 'cypress-tests-2/:id')
|
|
||||||
.and('contain', 'cypress-tests-2/new/row')
|
|
||||||
cy.get(".nav-items-container").contains("table-two").click()
|
|
||||||
cy.get(".nav-items-container").should('contain', 'table-two/:id')
|
|
||||||
.and('contain', 'table-two/new/row')
|
|
||||||
})
|
|
||||||
|
|
||||||
if (Cypress.env("TEST_ENV")) {
|
|
||||||
it("should generate data source screens", () => {
|
|
||||||
// Using MySQL data source for testing this
|
|
||||||
const datasource = "MySQL"
|
|
||||||
// Select & configure MySQL data source
|
|
||||||
cy.selectExternalDatasource(datasource)
|
|
||||||
cy.addDatasourceConfig(datasource)
|
|
||||||
// Create autogenerated screens from a MySQL table - MySQL contains books table
|
|
||||||
cy.createAutogeneratedScreens(["books"])
|
|
||||||
cy.get(".nav-items-container").contains("books").click()
|
|
||||||
cy.get(".nav-items-container").should('contain', 'books/:id')
|
|
||||||
.and('contain', 'books/new/row')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should generate internal table screens", () => {
|
||||||
|
// Create autogenerated screens from the internal table
|
||||||
|
cy.createAutogeneratedScreens(["Cypress Tests"])
|
||||||
|
// Confirm screens have been auto generated
|
||||||
|
cy.get(".nav-items-container").contains("cypress-tests").click({ force: true })
|
||||||
|
cy.get(".nav-items-container").should('contain', 'cypress-tests/:id')
|
||||||
|
.and('contain', 'cypress-tests/new/row')
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should generate multiple internal table screens at once", () => {
|
||||||
|
// Create a second internal table
|
||||||
|
const initialTable = "Cypress Tests"
|
||||||
|
const secondTable = "Table Two"
|
||||||
|
cy.createTable(secondTable)
|
||||||
|
// Create autogenerated screens from the internal tables
|
||||||
|
cy.createAutogeneratedScreens([initialTable, secondTable])
|
||||||
|
// Confirm screens have been auto generated
|
||||||
|
cy.get(".nav-items-container").contains("cypress-tests").click({ force: true })
|
||||||
|
// Previously generated tables are suffixed with numbers - as expected
|
||||||
|
cy.get(".nav-items-container").should('contain', 'cypress-tests-2/:id')
|
||||||
|
.and('contain', 'cypress-tests-2/new/row')
|
||||||
|
cy.get(".nav-items-container").contains("table-two").click()
|
||||||
|
cy.get(".nav-items-container").should('contain', 'table-two/:id')
|
||||||
|
.and('contain', 'table-two/new/row')
|
||||||
|
})
|
||||||
|
|
||||||
|
if (Cypress.env("TEST_ENV")) {
|
||||||
|
it("should generate data source screens", () => {
|
||||||
|
// Using MySQL data source for testing this
|
||||||
|
const datasource = "MySQL"
|
||||||
|
// Select & configure MySQL data source
|
||||||
|
cy.selectExternalDatasource(datasource)
|
||||||
|
cy.addDatasourceConfig(datasource)
|
||||||
|
// Create autogenerated screens from a MySQL table - MySQL contains books table
|
||||||
|
cy.createAutogeneratedScreens(["books"])
|
||||||
|
cy.get(".nav-items-container").contains("books").click()
|
||||||
|
cy.get(".nav-items-container").should('contain', 'books/:id')
|
||||||
|
.and('contain', 'books/new/row')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -11,7 +11,7 @@ filterTests(['smoke', 'all'], () => {
|
||||||
cy.createTestTableWithData()
|
cy.createTestTableWithData()
|
||||||
cy.wait(2000)
|
cy.wait(2000)
|
||||||
cy.contains("Automate").click()
|
cy.contains("Automate").click()
|
||||||
cy.get("[data-cy='new-screen'] > .spectrum-Icon").click()
|
cy.get(".add-button .spectrum-Icon").click()
|
||||||
cy.get(".modal-inner-wrapper").within(() => {
|
cy.get(".modal-inner-wrapper").within(() => {
|
||||||
cy.get("input").type("Add Row")
|
cy.get("input").type("Add Row")
|
||||||
cy.contains("Row Created").click({ force: true })
|
cy.contains("Row Created").click({ force: true })
|
||||||
|
|
|
@ -125,7 +125,7 @@ filterTests(['smoke', 'all'], () => {
|
||||||
|
|
||||||
it("renames a view", () => {
|
it("renames a view", () => {
|
||||||
cy.contains(".nav-item", "Test View")
|
cy.contains(".nav-item", "Test View")
|
||||||
.find(".actions .icon")
|
.find(".actions .icon.open-popover")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
cy.get(".spectrum-Menu-itemLabel").contains("Edit").click()
|
cy.get(".spectrum-Menu-itemLabel").contains("Edit").click()
|
||||||
cy.get(".modal-inner-wrapper").within(() => {
|
cy.get(".modal-inner-wrapper").within(() => {
|
||||||
|
@ -138,7 +138,7 @@ filterTests(['smoke', 'all'], () => {
|
||||||
|
|
||||||
it("deletes a view", () => {
|
it("deletes a view", () => {
|
||||||
cy.contains(".nav-item", "Test View Updated")
|
cy.contains(".nav-item", "Test View Updated")
|
||||||
.find(".actions .icon")
|
.find(".actions .icon.open-popover")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
cy.contains("Delete").click()
|
cy.contains("Delete").click()
|
||||||
cy.contains("Delete View").click()
|
cy.contains("Delete View").click()
|
||||||
|
|
|
@ -99,7 +99,7 @@ filterTests(['all'], () => {
|
||||||
cy.searchForApplication(originalName)
|
cy.searchForApplication(originalName)
|
||||||
cy.get(".appTable")
|
cy.get(".appTable")
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get("[data-cy='app-row-actions-menu']").eq(0).click()
|
cy.get("[aria-label='More']").eq(0).click()
|
||||||
})
|
})
|
||||||
// Check for when an app is published
|
// Check for when an app is published
|
||||||
if (published == true) {
|
if (published == true) {
|
||||||
|
|
|
@ -10,7 +10,7 @@ filterTests(['smoke', 'all'], () => {
|
||||||
it("should try to revert an unpublished app", () => {
|
it("should try to revert an unpublished app", () => {
|
||||||
// Click revert icon
|
// Click revert icon
|
||||||
cy.get(".toprightnav").within(() => {
|
cy.get(".toprightnav").within(() => {
|
||||||
cy.get("[data-cy='revert-application-topnav']").click({ force: true })
|
cy.get("[aria-label='Revert']").click({ force: true })
|
||||||
})
|
})
|
||||||
cy.get(".spectrum-Dialog-grid").within(() => {
|
cy.get(".spectrum-Dialog-grid").within(() => {
|
||||||
// Enter app name before revert
|
// Enter app name before revert
|
||||||
|
@ -41,7 +41,7 @@ filterTests(['smoke', 'all'], () => {
|
||||||
cy.addComponent("Elements", "Button")
|
cy.addComponent("Elements", "Button")
|
||||||
// Click Revert
|
// Click Revert
|
||||||
cy.get(".toprightnav").within(() => {
|
cy.get(".toprightnav").within(() => {
|
||||||
cy.get("[data-cy='revert-application-topnav']").click({ force: true })
|
cy.get("[aria-label='Revert']").click({ force: true })
|
||||||
})
|
})
|
||||||
cy.get(".spectrum-Dialog-grid").within(() => {
|
cy.get(".spectrum-Dialog-grid").within(() => {
|
||||||
// Click Revert
|
// Click Revert
|
||||||
|
@ -58,7 +58,7 @@ filterTests(['smoke', 'all'], () => {
|
||||||
it("should enter incorrect app name when reverting", () => {
|
it("should enter incorrect app name when reverting", () => {
|
||||||
// Click Revert
|
// Click Revert
|
||||||
cy.get(".toprightnav").within(() => {
|
cy.get(".toprightnav").within(() => {
|
||||||
cy.get("[data-cy='revert-application-topnav']").click({ force: true })
|
cy.get("[aria-label='Revert']").click({ force: true })
|
||||||
})
|
})
|
||||||
// Enter incorrect app name
|
// Enter incorrect app name
|
||||||
cy.get(".spectrum-Dialog-grid").within(() => {
|
cy.get(".spectrum-Dialog-grid").within(() => {
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
</Modal>
|
</Modal>
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<div class="add-button" data-cy="new-screen">
|
<div class="add-button">
|
||||||
<Icon hoverable name="AddCircle" on:click={modal.show} />
|
<Icon hoverable name="AddCircle" on:click={modal.show} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionMenu>
|
<ActionMenu>
|
||||||
<div slot="control" class="icon">
|
<div slot="control" class="icon open-popover">
|
||||||
<Icon s hoverable name="MoreSmallList" />
|
<Icon s hoverable name="MoreSmallList" />
|
||||||
</div>
|
</div>
|
||||||
<MenuItem icon="Edit" on:click={editorModal.show}>Edit</MenuItem>
|
<MenuItem icon="Edit" on:click={editorModal.show}>Edit</MenuItem>
|
||||||
|
|
|
@ -96,33 +96,31 @@
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<!-- Publish complete -->
|
<!-- Publish complete -->
|
||||||
<span class="publish-modal-wrap">
|
<Modal bind:this={publishCompleteModal}>
|
||||||
<Modal bind:this={publishCompleteModal}>
|
<ModalContent
|
||||||
<ModalContent
|
confirmText="Done"
|
||||||
confirmText="Done"
|
cancelText="View App"
|
||||||
cancelText="View App"
|
onCancel={viewApp}
|
||||||
onCancel={viewApp}
|
dataCy={"deploy-app-success-modal"}
|
||||||
dataCy={"deploy-app-success-modal"}
|
>
|
||||||
>
|
<div slot="header" class="app-published-header">
|
||||||
<div slot="header" class="app-published-header">
|
<svg
|
||||||
<svg
|
width="26px"
|
||||||
width="26px"
|
height="26px"
|
||||||
height="26px"
|
class="spectrum-Icon success-icon"
|
||||||
class="spectrum-Icon success-icon"
|
focusable="false"
|
||||||
focusable="false"
|
>
|
||||||
>
|
<use xlink:href="#spectrum-icon-18-GlobeCheck" />
|
||||||
<use xlink:href="#spectrum-icon-18-GlobeCheck" />
|
</svg>
|
||||||
</svg>
|
<span class="app-published-header-text">App Published!</span>
|
||||||
<span class="app-published-header-text">App Published!</span>
|
</div>
|
||||||
</div>
|
<CopyInput
|
||||||
<CopyInput
|
value={publishedUrl}
|
||||||
value={publishedUrl}
|
label="You can view your app at:"
|
||||||
label="You can view your app at:"
|
dataCy="deployed-app-url"
|
||||||
dataCy="deployed-app-url"
|
/>
|
||||||
/>
|
</ModalContent>
|
||||||
</ModalContent>
|
</Modal>
|
||||||
</Modal>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.app-published-header {
|
.app-published-header {
|
||||||
|
|
|
@ -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: application.appId,
|
||||||
|
eventSource: EventSource.PORTAL,
|
||||||
|
})
|
||||||
|
if (application.url) {
|
||||||
|
window.open(`/app${application.url}`)
|
||||||
|
} else {
|
||||||
|
window.open(`/${application.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: application.appId,
|
||||||
|
})
|
||||||
|
await API.unpublishApp(application.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>
|
|
@ -28,12 +28,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Icon
|
<Icon name="Revert" hoverable on:click={revertModal.show} />
|
||||||
name="Revert"
|
|
||||||
hoverable
|
|
||||||
on:click={revertModal.show}
|
|
||||||
dataCy="revert-application-topnav"
|
|
||||||
/>
|
|
||||||
<Modal bind:this={revertModal}>
|
<Modal bind:this={revertModal}>
|
||||||
<ModalContent
|
<ModalContent
|
||||||
title="Revert Changes"
|
title="Revert Changes"
|
||||||
|
|
|
@ -89,7 +89,7 @@
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<ActionMenu align="right" dataCy="app-row-actions-menu-popover">
|
<ActionMenu align="right" dataCy="app-row-actions-menu-popover">
|
||||||
<Icon hoverable slot="control" name="More" dataCy="app-row-actions-menu" />
|
<Icon hoverable slot="control" name="More" />
|
||||||
{#if app.lockedYou}
|
{#if app.lockedYou}
|
||||||
<MenuItem on:click={() => releaseLock(app)} icon="LockOpen">
|
<MenuItem on:click={() => releaseLock(app)} icon="LockOpen">
|
||||||
Release lock
|
Release lock
|
||||||
|
|
|
@ -1,67 +1,22 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, automationStore } from "builderStore"
|
import { store, automationStore } from "builderStore"
|
||||||
import { roles, flags } from "stores/backend"
|
import { roles, flags } from "stores/backend"
|
||||||
import {
|
import { Icon, ActionGroup, Tabs, Tab, notifications } from "@budibase/bbui"
|
||||||
Icon,
|
|
||||||
ActionGroup,
|
|
||||||
Tabs,
|
|
||||||
Tab,
|
|
||||||
notifications,
|
|
||||||
PopoverMenu,
|
|
||||||
Layout,
|
|
||||||
Button,
|
|
||||||
Heading,
|
|
||||||
Body,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import DeployModal from "components/deploy/DeployModal.svelte"
|
|
||||||
import RevertModal from "components/deploy/RevertModal.svelte"
|
import RevertModal from "components/deploy/RevertModal.svelte"
|
||||||
import VersionModal from "components/deploy/VersionModal.svelte"
|
import VersionModal from "components/deploy/VersionModal.svelte"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import DeployNavigation from "components/deploy/DeployNavigation.svelte"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { auth, admin, apps } from "stores/portal"
|
import { auth, admin } from "stores/portal"
|
||||||
import { isActive, goto, layout, redirect } from "@roxi/routify"
|
import { isActive, goto, layout, redirect } from "@roxi/routify"
|
||||||
import Logo from "assets/bb-emblem.svg"
|
import Logo from "assets/bb-emblem.svg"
|
||||||
import { capitalise } from "helpers"
|
import { capitalise } from "helpers"
|
||||||
import UpgradeModal from "components/upgrade/UpgradeModal.svelte"
|
import UpgradeModal from "components/upgrade/UpgradeModal.svelte"
|
||||||
import { onMount, onDestroy } from "svelte"
|
import { onMount, onDestroy } from "svelte"
|
||||||
import { processStringSync } from "@budibase/string-templates"
|
|
||||||
import { checkIncomingDeploymentStatus } from "components/deploy/utils"
|
|
||||||
import analytics, { Events, EventSource } from "analytics"
|
|
||||||
|
|
||||||
export let application
|
export let application
|
||||||
|
|
||||||
// Get Package and set store
|
// Get Package and set store
|
||||||
let promise = getPackage()
|
let promise = getPackage()
|
||||||
let unpublishModal
|
|
||||||
let publishPopover
|
|
||||||
|
|
||||||
$: 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)
|
|
||||||
|
|
||||||
$: isPublished =
|
|
||||||
selectedApp.deployed && latestDeployments && latestDeployments?.length
|
|
||||||
? true
|
|
||||||
: false
|
|
||||||
|
|
||||||
// Sync once when you load the app
|
// Sync once when you load the app
|
||||||
let hasSynced = false
|
let hasSynced = false
|
||||||
|
@ -73,18 +28,6 @@
|
||||||
window.open(`/${application}`)
|
window.open(`/${application}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
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}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getPackage() {
|
async function getPackage() {
|
||||||
try {
|
try {
|
||||||
const pkg = await API.fetchAppPackage(application)
|
const pkg = await API.fetchAppPackage(application)
|
||||||
|
@ -115,74 +58,20 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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 () => {
|
onMount(async () => {
|
||||||
if (!hasSynced && application) {
|
if (!hasSynced && application) {
|
||||||
try {
|
try {
|
||||||
await API.syncApp(application)
|
await API.syncApp(application)
|
||||||
await apps.load()
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Failed to sync with production database")
|
notifications.error("Failed to sync with production database")
|
||||||
}
|
}
|
||||||
hasSynced = true
|
hasSynced = true
|
||||||
}
|
}
|
||||||
deployments = await fetchDeployments()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
store.actions.reset()
|
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 {
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await promise}
|
{#await promise}
|
||||||
|
@ -223,84 +112,16 @@
|
||||||
<VersionModal />
|
<VersionModal />
|
||||||
<RevertModal />
|
<RevertModal />
|
||||||
<Icon name="Play" hoverable on:click={previewApp} />
|
<Icon name="Play" hoverable on:click={previewApp} />
|
||||||
|
<DeployNavigation {application} />
|
||||||
{#if isPublished}
|
|
||||||
<PopoverMenu
|
|
||||||
bind:this={publishPopover}
|
|
||||||
align="right"
|
|
||||||
disabled={!isPublished}
|
|
||||||
dataCy="publish-popover-menu"
|
|
||||||
>
|
|
||||||
<div slot="control" class="icon app-status-icon">
|
|
||||||
<Icon
|
|
||||||
size="M"
|
|
||||||
hoverable
|
|
||||||
name="Globe"
|
|
||||||
disabled={!isPublished}
|
|
||||||
tooltip="Your published app"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Layout gap="M">
|
|
||||||
<Heading size="XS">Your published app</Heading>
|
|
||||||
<Body size="S">
|
|
||||||
{#if isPublished}
|
|
||||||
{processStringSync(
|
|
||||||
"Last published {{ duration time 'millisecond' }} ago",
|
|
||||||
{
|
|
||||||
time:
|
|
||||||
new Date().getTime() -
|
|
||||||
new Date(latestDeployments[0].updatedAt).getTime(),
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
{/if}
|
|
||||||
</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>
|
|
||||||
</PopoverMenu>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if !isPublished}
|
|
||||||
<Icon
|
|
||||||
size="M"
|
|
||||||
name="GlobeStrike"
|
|
||||||
disabled
|
|
||||||
tooltip="Your app has not been published yet"
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<DeployModal onOk={completePublish} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<slot />
|
<slot />
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
{:catch error}
|
{:catch error}
|
||||||
<p>Something went wrong: {error.message}</p>
|
<p>Something went wrong: {error.message}</p>
|
||||||
{/await}
|
{/await}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.publish-popover-actions :global([data-cy="publish-popover-action"]) {
|
|
||||||
margin-right: var(--spacing-s);
|
|
||||||
}
|
|
||||||
.loading {
|
.loading {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
Loading…
Reference in New Issue