You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ya...@apache.org on 2026/04/25 05:16:44 UTC
(airavata-portals) 27/34: feat(launcher): confirm prompts on app/interface/project change
This is an automated email from the ASF dual-hosted git repository.
yasithdev pushed a commit to branch feat/generic-experiment-launcher
in repository https://gitbox.apache.org/repos/asf/airavata-portals.git
commit e80fff597ece8c59972f1a725f26373b9c578343
Author: yasithdev <ya...@icloud.com>
AuthorDate: Fri Apr 24 23:06:56 2026 -0400
feat(launcher): confirm prompts on app/interface/project change
---
.../js/components/launch/AppPicker.vue | 12 +++++++++++-
.../js/components/launch/ExperimentMetaHeader.vue | 17 ++++++++++++++++-
.../js/components/launch/InterfacePicker.vue | 16 +++++++++++++++-
.../js/composables/useConfirmReset.ts | 5 +++++
.../tests/unit/composables/useConfirmReset.spec.ts | 19 +++++++++++++++++++
5 files changed, 66 insertions(+), 3 deletions(-)
diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/AppPicker.vue b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/AppPicker.vue
index 7df9ef1ad..b14af9bf0 100644
--- a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/AppPicker.vue
+++ b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/AppPicker.vue
@@ -46,6 +46,7 @@
import { computed, ref } from "vue";
import type { Application } from "django-airavata-common-ui/js/stores/launch-types";
import { useLaunchStore } from "django-airavata-common-ui/js/stores/launch";
+import { useConfirmReset } from "../../composables/useConfirmReset";
const props = defineProps<{ applications: Application[] }>();
const store = useLaunchStore();
@@ -74,7 +75,16 @@ const filtered = computed(() => {
return xs;
});
+const guardedPick = useConfirmReset(
+ "Switching app clears interface, inputs, and outputs. Continue?",
+ (a: Application) => store.pickApp(a),
+);
+
function pick(a: Application) {
- store.pickApp(a);
+ if (store.draft.app_id && store.draft.app_id !== a.app_id) {
+ guardedPick(a);
+ } else {
+ store.pickApp(a);
+ }
}
</script>
diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/ExperimentMetaHeader.vue b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/ExperimentMetaHeader.vue
index 52fddd29b..2e9e030e2 100644
--- a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/ExperimentMetaHeader.vue
+++ b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/ExperimentMetaHeader.vue
@@ -38,6 +38,7 @@
<script setup lang="ts">
import { useLaunchStore } from "django-airavata-common-ui/js/stores/launch";
+import { useConfirmReset } from "../../composables/useConfirmReset";
defineProps<{ projects: Array<{ project_id: string; name: string }> }>();
@@ -46,9 +47,23 @@ const store = useLaunchStore();
function onName(v: string) {
store.setMeta({ name: v, project_id: store.draft.project_id, description: store.draft.description });
}
+
+const guardedProject = useConfirmReset(
+ "Switching project clears your runtime selections. Continue?",
+ (id: string | null) => {
+ store.setMeta({ name: store.draft.name, project_id: id, description: store.draft.description });
+ },
+);
+
function onProject(v: string) {
- store.setMeta({ name: store.draft.name, project_id: v || null, description: store.draft.description });
+ const next = v || null;
+ if (store.draft.project_id && store.draft.project_id !== next) {
+ guardedProject(next);
+ } else {
+ store.setMeta({ name: store.draft.name, project_id: next, description: store.draft.description });
+ }
}
+
function onDescription(v: string) {
store.setMeta({ name: store.draft.name, project_id: store.draft.project_id, description: v });
}
diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/InterfacePicker.vue b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/InterfacePicker.vue
index 5216b013a..e56aa94ac 100644
--- a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/InterfacePicker.vue
+++ b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/InterfacePicker.vue
@@ -11,7 +11,7 @@
class="card w-100 text-start p-2"
:class="{ 'border-primary': store.draft.interface_name === iface.name }"
:data-test="`iface-card-${iface.name}`"
- @click="store.pickInterface(iface.name)"
+ @click="onPick(iface.name)"
>
<code class="d-block fw-bold">{{ iface.name }}</code>
<small class="text-muted" :data-test="`iface-sig-${iface.name}`">
@@ -25,10 +25,24 @@
<script setup lang="ts">
import type { IODescriptor } from "django-airavata-common-ui/js/stores/launch-types";
import { useLaunchStore } from "django-airavata-common-ui/js/stores/launch";
+import { useConfirmReset } from "../../composables/useConfirmReset";
const store = useLaunchStore();
function formatList(io: IODescriptor[]): string {
return io.map((x) => `${x.name}: ${x.type}`).join(", ");
}
+
+const guarded = useConfirmReset(
+ "Switching interface clears inputs and outputs. Continue?",
+ (n: string) => store.pickInterface(n),
+);
+
+function onPick(n: string) {
+ if (store.draft.interface_name && store.draft.interface_name !== n) {
+ guarded(n);
+ } else {
+ store.pickInterface(n);
+ }
+}
</script>
diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/composables/useConfirmReset.ts b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/composables/useConfirmReset.ts
new file mode 100644
index 000000000..0f0285e66
--- /dev/null
+++ b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/composables/useConfirmReset.ts
@@ -0,0 +1,5 @@
+export function useConfirmReset<TArgs extends unknown[]>(message: string, fn: (...a: TArgs) => void) {
+ return (...args: TArgs) => {
+ if (window.confirm(message)) fn(...args);
+ };
+}
diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/composables/useConfirmReset.spec.ts b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/composables/useConfirmReset.spec.ts
new file mode 100644
index 000000000..a462cce2c
--- /dev/null
+++ b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/composables/useConfirmReset.spec.ts
@@ -0,0 +1,19 @@
+import { describe, expect, it, vi } from "vitest";
+import { useConfirmReset } from "../../../js/composables/useConfirmReset";
+
+describe("useConfirmReset", () => {
+ it("calls onConfirm when window.confirm returns true", () => {
+ vi.spyOn(window, "confirm").mockReturnValueOnce(true);
+ const fn = vi.fn();
+ const guarded = useConfirmReset("ok?", fn);
+ guarded();
+ expect(fn).toHaveBeenCalledTimes(1);
+ });
+
+ it("skips onConfirm when window.confirm returns false", () => {
+ vi.spyOn(window, "confirm").mockReturnValueOnce(false);
+ const fn = vi.fn();
+ useConfirmReset("ok?", fn)();
+ expect(fn).not.toHaveBeenCalled();
+ });
+});