From 18f09f4e13818ef0e9455488270524178a1b0ca3 Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 22 Feb 2024 15:00:16 +0000 Subject: [PATCH] Duplicate app behaviour and test updates --- .../backend-core/src/events/publishers/app.ts | 13 ++ .../tests/core/utilities/mocks/events.ts | 1 + packages/bbui/src/Form/Core/index.js | 1 + .../src/components/deploy/DeleteModal.svelte | 40 ++++- .../src/components/start/AppRow.svelte | 7 +- .../src/components/start/AppRowContext.svelte | 71 ++++++++ .../components/start/DuplicateAppModal.svelte | 156 ++++++++++++++++++ .../components/start/ExportAppModal.svelte | 1 + .../app/[application]/settings/_layout.svelte | 8 +- packages/frontend-core/src/api/app.js | 12 ++ .../server/src/api/controllers/application.ts | 74 ++++++++- packages/server/src/api/routes/application.ts | 5 + .../src/api/routes/tests/application.spec.ts | 95 +++++++++++ .../server/src/sdk/app/backups/imports.ts | 2 +- .../src/tests/utilities/TestConfiguration.ts | 8 +- packages/types/src/sdk/events/app.ts | 8 + packages/types/src/sdk/events/event.ts | 2 + 17 files changed, 484 insertions(+), 20 deletions(-) create mode 100644 packages/builder/src/components/start/AppRowContext.svelte create mode 100644 packages/builder/src/components/start/DuplicateAppModal.svelte diff --git a/packages/backend-core/src/events/publishers/app.ts b/packages/backend-core/src/events/publishers/app.ts index d08d59b5f1..af26b09e72 100644 --- a/packages/backend-core/src/events/publishers/app.ts +++ b/packages/backend-core/src/events/publishers/app.ts @@ -13,6 +13,7 @@ import { AppVersionRevertedEvent, AppRevertedEvent, AppExportedEvent, + AppDuplicatedEvent, } from "@budibase/types" const created = async (app: App, timestamp?: string | number) => { @@ -77,6 +78,17 @@ async function fileImported(app: App) { await publishEvent(Event.APP_FILE_IMPORTED, properties) } +async function duplicated(app: App, duplicateAppId: string) { + const properties: AppDuplicatedEvent = { + duplicateAppId, + appId: app.appId, + audited: { + name: app.name, + }, + } + await publishEvent(Event.APP_DUPLICATED, properties) +} + async function templateImported(app: App, templateKey: string) { const properties: AppTemplateImportedEvent = { appId: app.appId, @@ -147,6 +159,7 @@ export default { published, unpublished, fileImported, + duplicated, templateImported, versionUpdated, versionReverted, diff --git a/packages/backend-core/tests/core/utilities/mocks/events.ts b/packages/backend-core/tests/core/utilities/mocks/events.ts index fef730768a..96f351de10 100644 --- a/packages/backend-core/tests/core/utilities/mocks/events.ts +++ b/packages/backend-core/tests/core/utilities/mocks/events.ts @@ -15,6 +15,7 @@ beforeAll(async () => { jest.spyOn(events.app, "created") jest.spyOn(events.app, "updated") + jest.spyOn(events.app, "duplicated") jest.spyOn(events.app, "deleted") jest.spyOn(events.app, "published") jest.spyOn(events.app, "unpublished") diff --git a/packages/bbui/src/Form/Core/index.js b/packages/bbui/src/Form/Core/index.js index b0edf52748..3e5befca0b 100644 --- a/packages/bbui/src/Form/Core/index.js +++ b/packages/bbui/src/Form/Core/index.js @@ -14,3 +14,4 @@ export { default as CoreStepper } from "./Stepper.svelte" export { default as CoreRichTextField } from "./RichTextField.svelte" export { default as CoreSlider } from "./Slider.svelte" export { default as CoreFile } from "./File.svelte" +export { default as CoreEnv } from "./EnvSwitch.svelte" diff --git a/packages/builder/src/components/deploy/DeleteModal.svelte b/packages/builder/src/components/deploy/DeleteModal.svelte index 855f6a0757..293d0adf60 100644 --- a/packages/builder/src/components/deploy/DeleteModal.svelte +++ b/packages/builder/src/components/deploy/DeleteModal.svelte @@ -3,9 +3,16 @@ import { goto } from "@roxi/routify" import ConfirmDialog from "components/common/ConfirmDialog.svelte" import { apps } from "stores/portal" - import { appStore } from "stores/builder" import { API } from "api" + export let appId + export let appName + export let onDeleteSuccess = () => { + $goto("/builder") + } + + let deleting = false + export const show = () => { deletionModal.show() } @@ -17,14 +24,24 @@ let deletionModal let deletionConfirmationAppName + const copyName = () => { + deletionConfirmationAppName = appName + } + const deleteApp = async () => { + if (!appId) { + console.log("No app id provided") + return + } + deleting = true try { - await API.deleteApp($appStore.appId) + await API.deleteApp(appId) apps.load() notifications.success("App deleted successfully") - $goto("/builder") + onDeleteSuccess() } catch (err) { notifications.error("Error deleting app") + deleting = false } } @@ -35,14 +52,19 @@ okText="Delete" onOk={deleteApp} onCancel={() => (deletionConfirmationAppName = null)} - disabled={deletionConfirmationAppName !== $appStore.name} + disabled={deletionConfirmationAppName !== appName || deleting} > - Are you sure you want to delete {$appStore.name}? + + Are you sure you want to delete + {appName}?
Please enter the app name below to confirm.

- + + + diff --git a/packages/builder/src/components/start/AppRow.svelte b/packages/builder/src/components/start/AppRow.svelte index c05ae4c624..dd23487870 100644 --- a/packages/builder/src/components/start/AppRow.svelte +++ b/packages/builder/src/components/start/AppRow.svelte @@ -5,6 +5,7 @@ import { goto } from "@roxi/routify" import { UserAvatars } from "@budibase/frontend-core" import { sdk } from "@budibase/shared-core" + import AppRowContext from "./AppRowContext.svelte" export let app export let lockedAction @@ -74,12 +75,10 @@ {#if isBuilder}
- - +
{:else if app.deployed} diff --git a/packages/builder/src/components/start/AppRowContext.svelte b/packages/builder/src/components/start/AppRowContext.svelte new file mode 100644 index 0000000000..f2b9d2e04c --- /dev/null +++ b/packages/builder/src/components/start/AppRowContext.svelte @@ -0,0 +1,71 @@ + + + {}} +/> + + + + + + + + + + +
+ +
+ { + duplicateModal.show() + }} + > + Duplicate + + { + exportPublishedVersion = false + exportModal.show() + }} + > + Export latest edited app + + {#if app.deployed} + { + exportPublishedVersion = true + exportModal.show() + }} + > + Export latest published app + + {/if} + { + deleteModal.show() + }} + > + Delete + +
diff --git a/packages/builder/src/components/start/DuplicateAppModal.svelte b/packages/builder/src/components/start/DuplicateAppModal.svelte new file mode 100644 index 0000000000..9a83f57215 --- /dev/null +++ b/packages/builder/src/components/start/DuplicateAppModal.svelte @@ -0,0 +1,156 @@ + + + { + validation.check({ + ...$values, + }) + if ($validation.valid) { + await duplicateApp() + } else { + return keepOpen + } + }} +> + + ($validation.touched.name = true)} + on:change={nameToUrl($values.name)} + label="Name" + placeholder={defaultAppName} + /> + + ($validation.touched.url = true)} + on:change={tidyUrl($values.url)} + label="URL" + placeholder={$values.url + ? $values.url + : `/${resolveAppUrl($values.name)}`} + /> + {#if $values.url && $values.url !== "" && !$validation.errors.url} +
+ {appUrl} +
+ {/if} +
+
+
+ + diff --git a/packages/builder/src/components/start/ExportAppModal.svelte b/packages/builder/src/components/start/ExportAppModal.svelte index 734e4448a1..ec0cf42fe0 100644 --- a/packages/builder/src/components/start/ExportAppModal.svelte +++ b/packages/builder/src/components/start/ExportAppModal.svelte @@ -121,6 +121,7 @@ @@ -67,7 +67,11 @@ - +