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 @@ - +