Fix loading states and issues with overview tab

This commit is contained in:
Andrew Kingston 2022-08-08 12:08:37 +01:00
parent 3d42c9cfeb
commit 50c04c801f
2 changed files with 225 additions and 225 deletions

View File

@ -34,15 +34,19 @@
export let application
let promise = getPackage()
let loaded = false
let deletionModal
let unpublishModal
let appName = ""
let deployments = []
// App
$: filteredApps = $apps.filter(app => app.devId === application)
$: selectedApp = filteredApps?.length ? filteredApps[0] : null
$: loaded && !selectedApp && backToAppList()
$: isPublished =
selectedApp?.status === AppStatus.DEPLOYED && latestDeployments?.length > 0
$: appUrl = `${window.origin}/app${selectedApp?.url}`
// Locking
$: lockedBy = selectedApp?.lockedBy
@ -54,18 +58,11 @@
}`
// App deployments
$: deployments = []
$: latestDeployments = deployments
.filter(
deployment =>
deployment.status === "SUCCESS" && application === deployment.appId
)
.filter(x => x.status === "SUCCESS" && application === x.appId)
.sort((a, b) => a.updatedAt > b.updatedAt)
$: isPublished =
selectedApp?.status === AppStatus.DEPLOYED && latestDeployments?.length > 0
$: appUrl = `${window.origin}/app${selectedApp?.url}`
// Tabs
$: tabs = ["Overview", "Automation History", "Backups", "Settings", "Access"]
$: selectedTab = "Overview"
@ -83,17 +80,6 @@
}
}
async function getPackage() {
try {
const pkg = await API.fetchAppPackage(application)
await store.actions.initialise(pkg)
loaded = true
return pkg
} catch (error) {
notifications.error(`Error initialising app: ${error?.message}`)
}
}
const reviewPendingDeployments = (deployments, newDeployments) => {
if (deployments.length > 0) {
const pending = checkIncomingDeploymentStatus(deployments, newDeployments)
@ -185,185 +171,190 @@
appName = null
}
onDestroy(() => {
store.actions.reset()
})
onMount(async () => {
const params = new URLSearchParams(window.location.search)
if (params.get("tab")) {
selectedTab = params.get("tab")
}
// Check app exists
try {
const pkg = await API.fetchAppPackage(application)
await store.actions.initialise(pkg)
} catch (error) {
// Swallow
backToAppList()
}
// Initialise application
try {
await API.syncApp(application)
deployments = await fetchDeployments()
if (!apps.length) {
await apps.load()
}
await API.syncApp(application)
deployments = await fetchDeployments()
} catch (error) {
notifications.error("Error initialising app overview")
}
loaded = true
})
onDestroy(() => {
store.actions.reset()
})
</script>
{#if selectedApp}
<span class="overview-wrap">
<Page wide noPadding>
{#await promise}
<div class="loading">
<ProgressCircle size="XL" />
</div>
{:then _}
<Layout paddingX="XXL" paddingY="XL" gap="L">
<span class="page-header" class:loaded>
<ActionButton secondary icon={"ArrowLeft"} on:click={backToAppList}>
Back
</ActionButton>
</span>
<div class="overview-header">
<div class="app-title">
<div class="app-logo">
<div
class="app-icon"
style="color: {selectedApp?.icon?.color || ''}"
>
<EditableIcon
app={selectedApp}
size="XL"
name={selectedApp?.icon?.name || "Apps"}
/>
</div>
</div>
<div class="app-details">
<Heading size="M">{selectedApp?.name}</Heading>
<div class="app-url">{appUrl}</div>
<span class="overview-wrap">
<Page wide noPadding>
{#if !loaded || !selectedApp}
<div class="loading">
<ProgressCircle size="XL" />
</div>
{:else}
<Layout paddingX="XXL" paddingY="XL" gap="L">
<span class="page-header" class:loaded>
<ActionButton secondary icon={"ArrowLeft"} on:click={backToAppList}>
Back
</ActionButton>
</span>
<div class="overview-header">
<div class="app-title">
<div class="app-logo">
<div
class="app-icon"
style="color: {selectedApp?.icon?.color || ''}"
>
<EditableIcon
app={selectedApp}
size="XL"
name={selectedApp?.icon?.name || "Apps"}
/>
</div>
</div>
<div class="header-right">
<AppLockModal app={selectedApp} />
<ButtonGroup gap="XS">
<Button
size="M"
quiet
secondary
icon="Globe"
disabled={!isPublished}
on:click={viewApp}
dataCy="view-app"
>
View app
</Button>
<Button
size="M"
cta
icon="Edit"
disabled={lockedBy && !lockedByYou}
on:click={() => {
editApp(selectedApp)
}}
>
<span>Edit</span>
</Button>
</ButtonGroup>
<ActionMenu align="right" dataCy="app-overview-menu-popover">
<span slot="control" class="app-overview-actions-icon">
<Icon hoverable name="More" />
</span>
<MenuItem
on:click={() => exportApp(selectedApp, { published: false })}
icon="DownloadFromCloud"
>
Export latest
</MenuItem>
{#if isPublished}
<MenuItem
on:click={() => exportApp(selectedApp, { published: true })}
icon="DownloadFromCloudOutline"
>
Export published
</MenuItem>
<MenuItem on:click={() => copyAppId(selectedApp)} icon="Copy">
Copy app ID
</MenuItem>
{/if}
{#if !isPublished}
<MenuItem
on:click={() => deleteApp(selectedApp)}
icon="Delete"
>
Delete
</MenuItem>
{/if}
</ActionMenu>
<div class="app-details">
<Heading size="M">{selectedApp?.name}</Heading>
<div class="app-url">{appUrl}</div>
</div>
</div>
</Layout>
<div class="tab-wrap">
<Tabs
selected={selectedTab}
noPadding
on:select={e => {
selectedTab = e.detail
}}
>
<Tab title="Overview">
<OverviewTab
app={selectedApp}
deployments={latestDeployments}
navigateTab={handleTabChange}
on:unpublish={e => unpublishApp(e.detail)}
/>
</Tab>
<Tab title="Access">
<AccessTab app={selectedApp} />
</Tab>
{#if isPublished}
<Tab title="Automation History">
<HistoryTab app={selectedApp} />
</Tab>
{/if}
{#if false}
<Tab title="Backups">
<div class="container">Backups contents</div>
</Tab>
{/if}
<Tab title="Settings">
<SettingsTab app={selectedApp} />
</Tab>
</Tabs>
<div class="header-right">
<AppLockModal app={selectedApp} />
<ButtonGroup gap="XS">
<Button
size="M"
quiet
secondary
icon="Globe"
disabled={!isPublished}
on:click={viewApp}
dataCy="view-app"
>
View app
</Button>
<Button
size="M"
cta
icon="Edit"
disabled={lockedBy && !lockedByYou}
on:click={() => {
editApp(selectedApp)
}}
>
<span>Edit</span>
</Button>
</ButtonGroup>
<ActionMenu align="right" dataCy="app-overview-menu-popover">
<span slot="control" class="app-overview-actions-icon">
<Icon hoverable name="More" />
</span>
<MenuItem
on:click={() => exportApp(selectedApp, { published: false })}
icon="DownloadFromCloud"
>
Export latest
</MenuItem>
{#if isPublished}
<MenuItem
on:click={() => exportApp(selectedApp, { published: true })}
icon="DownloadFromCloudOutline"
>
Export published
</MenuItem>
<MenuItem on:click={() => copyAppId(selectedApp)} icon="Copy">
Copy app ID
</MenuItem>
{/if}
{#if !isPublished}
<MenuItem on:click={() => deleteApp(selectedApp)} icon="Delete">
Delete
</MenuItem>
{/if}
</ActionMenu>
</div>
</div>
<ConfirmDialog
bind:this={deletionModal}
title="Confirm deletion"
okText="Delete app"
onOk={confirmDeleteApp}
onCancel={() => (appName = null)}
disabled={appName !== selectedApp?.name}
</Layout>
<div class="tab-wrap">
<Tabs
selected={selectedTab}
noPadding
on:select={e => {
selectedTab = e.detail
}}
>
Are you sure you want to delete the app <b>{selectedApp?.name}</b>?
<Tab title="Overview">
<OverviewTab
app={selectedApp}
deployments={latestDeployments}
navigateTab={handleTabChange}
on:unpublish={e => unpublishApp(e.detail)}
/>
</Tab>
<Tab title="Access">
<AccessTab app={selectedApp} />
</Tab>
{#if isPublished}
<Tab title="Automation History">
<HistoryTab app={selectedApp} />
</Tab>
{/if}
{#if false}
<Tab title="Backups">
<div class="container">Backups contents</div>
</Tab>
{/if}
<Tab title="Settings">
<SettingsTab app={selectedApp} />
</Tab>
</Tabs>
</div>
<ConfirmDialog
bind:this={deletionModal}
title="Confirm deletion"
okText="Delete app"
onOk={confirmDeleteApp}
onCancel={() => (appName = null)}
disabled={appName !== selectedApp?.name}
>
Are you sure you want to delete the app <b>{selectedApp?.name}</b>?
<p>Please enter the app name below to confirm.</p>
<Input
bind:value={appName}
data-cy="delete-app-confirmation"
placeholder={selectedApp?.name}
/>
</ConfirmDialog>
<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>
{:catch error}
<p>Something went wrong: {error.message}</p>
{/await}
</Page>
</span>
{/if}
<p>Please enter the app name below to confirm.</p>
<Input
bind:value={appName}
data-cy="delete-app-confirmation"
placeholder={selectedApp?.name}
/>
</ConfirmDialog>
<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>
{/if}
</Page>
</span>
<style>
.app-url {

View File

@ -6,17 +6,26 @@
import clientPackage from "@budibase/client/package.json"
import { processStringSync } from "@budibase/string-templates"
import { users, auth, apps } from "stores/portal"
import { createEventDispatcher, onMount } from "svelte"
import { createEventDispatcher } from "svelte"
import { fetchData } from "@budibase/frontend-core"
import { API } from "api"
export let app
export let deployments
export let navigateTab
let userCount
const dispatch = createEventDispatcher()
const unpublishApp = () => {
dispatch("unpublish", app)
}
const dispatch = createEventDispatcher()
const appUsersFetch = fetchData({
API,
datasource: {
type: "user",
},
options: {
query: {
appId: apps.getProdAppID(app.devId),
},
},
})
let appEditor, appEditorPromise
@ -25,6 +34,11 @@
$: appEditorId = !app?.updatedBy ? $auth.user._id : app?.updatedBy
$: appEditorText = appEditor?.firstName || appEditor?.email
$: fetchAppEditor(appEditorId)
$: appUsers = $appUsersFetch.rows || []
const unpublishApp = () => {
dispatch("unpublish", app)
}
async function fetchAppEditor(editorId) {
appEditorPromise = users.get(editorId)
@ -36,16 +50,8 @@
initials += user.firstName ? user.firstName[0] : ""
initials += user.lastName ? user.lastName[0] : ""
return initials == "" ? user.email[0] : initials
return initials === "" ? user.email[0] : initials
}
onMount(async () => {
let resp = await users.getUserCountByApp({
appId: apps.getProdAppID(app.devId),
})
userCount = resp.userCount
await users.search({ appId: apps.getProdAppID(app.devId), limit: 4 })
})
</script>
<div class="overview-tab">
@ -141,38 +147,41 @@
{/if}
</div>
</DashCard>
<DashCard
title={"Access"}
showIcon={true}
action={() => {
navigateTab("Access")
}}
dataCy={"access"}
>
<div class="last-edited-content">
{#if $users?.data?.length}
<Layout noPadding gap="S">
<div class="users-tab">
{#each $users?.data as user}
<Avatar size="M" initials={getInitials(user)} />
{/each}
</div>
{#if $appUsersFetch.loaded}
<DashCard
title={"Access"}
showIcon={true}
action={() => {
navigateTab("Access")
}}
dataCy={"access"}
>
<div class="last-edited-content">
{#if appUsers.length}
<Layout noPadding gap="S">
<div class="users-tab">
{#each appUsers.slice(0, 4) as user}
<Avatar size="M" initials={getInitials(user)} />
{/each}
</div>
<div class="users-text">
{userCount}
{userCount > 1 ? `users have` : `user has`} access to this app
</div>
</Layout>
{:else}
<Layout noPadding gap="S">
<Body>No users</Body>
<div class="users-text">
No users have been assigned to this app
</div>
</Layout>
{/if}
</div>
</DashCard>
<div class="users-text">
{appUsers.length}
{appUsers.length > 1 ? `users have` : `user has`} access to this
app
</div>
</Layout>
{:else}
<Layout noPadding gap="S">
<Body>No users</Body>
<div class="users-text">
No users have been assigned to this app
</div>
</Layout>
{/if}
</div>
</DashCard>
{/if}
</div>
{#if false}
<div class="bottom">
@ -229,7 +238,7 @@
.users-tab {
display: flex;
gap: var(--spacing-m);
gap: var(--spacing-s);
}
.users-text {