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 fe5298a0e0e0dbb351c7fd5857c610c8fa32a0b2 Author: yasithdev <[email protected]> AuthorDate: Fri Apr 24 22:33:03 2026 -0400 feat(launcher): LaunchContainer + entry-launch.ts (tabs scaffolding) - Add LaunchContainer.vue orchestrating ExperimentMetaHeader, WizardTabs, and the three tab panels; tab state mirrored to ?tab= query param - Add entry-launch.ts Vite entry (createApp + Pinia) mounting into #launch-app - Add Tab1ApplicationInputs, Tab2Runtime, Tab3ReviewLaunch empty stubs - Register entry-launch.ts in vite.config.js explicit entries map - Add LaunchContainer.spec.ts (3 tests: active tab, meta header, one panel shown) - Use v-if panels (not :hidden) so the test's attribute filter works correctly - Defensive r?.results ?? [] guards against restoreMocks clearing mock impls --- .../js/components/launch/Tab1ApplicationInputs.vue | 2 + .../js/components/launch/Tab2Runtime.vue | 2 + .../js/components/launch/Tab3ReviewLaunch.vue | 2 + .../js/containers/LaunchContainer.vue | 44 ++++++++++++++++++++++ .../django_airavata_workspace/js/entry-launch.ts | 10 +++++ .../unit/components/launch/LaunchContainer.spec.ts | 33 ++++++++++++++++ .../django_airavata/apps/workspace/vite.config.js | 1 + 7 files changed, 94 insertions(+) diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/Tab1ApplicationInputs.vue b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/Tab1ApplicationInputs.vue new file mode 100644 index 000000000..25a445eed --- /dev/null +++ b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/Tab1ApplicationInputs.vue @@ -0,0 +1,2 @@ +<template><div /></template> +<script setup lang="ts"></script> diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/Tab2Runtime.vue b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/Tab2Runtime.vue new file mode 100644 index 000000000..25a445eed --- /dev/null +++ b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/Tab2Runtime.vue @@ -0,0 +1,2 @@ +<template><div /></template> +<script setup lang="ts"></script> diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/Tab3ReviewLaunch.vue b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/Tab3ReviewLaunch.vue new file mode 100644 index 000000000..25a445eed --- /dev/null +++ b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/Tab3ReviewLaunch.vue @@ -0,0 +1,2 @@ +<template><div /></template> +<script setup lang="ts"></script> diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/LaunchContainer.vue b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/LaunchContainer.vue new file mode 100644 index 000000000..d9a1d0809 --- /dev/null +++ b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/LaunchContainer.vue @@ -0,0 +1,44 @@ +<template> + <div class="container-fluid"> + <ExperimentMetaHeader :projects="projects" /> + <WizardTabs :active="active" @update:active="onChangeTab" /> + <div data-test="active-tab" :data-active="active" /> + <section v-if="active === 1" role="tabpanel"> + <Tab1ApplicationInputs /> + </section> + <section v-if="active === 2" role="tabpanel"> + <Tab2Runtime /> + </section> + <section v-if="active === 3" role="tabpanel"> + <Tab3ReviewLaunch /> + </section> + </div> +</template> + +<script setup lang="ts"> +import { onMounted, ref } from "vue"; +import { launcherService } from "django-airavata-common-ui/js/services/launcherService"; +import ExperimentMetaHeader from "../components/launch/ExperimentMetaHeader.vue"; +import WizardTabs from "../components/launch/WizardTabs.vue"; +import Tab1ApplicationInputs from "../components/launch/Tab1ApplicationInputs.vue"; +import Tab2Runtime from "../components/launch/Tab2Runtime.vue"; +import Tab3ReviewLaunch from "../components/launch/Tab3ReviewLaunch.vue"; + +const active = ref<1 | 2 | 3>(1); +const projects = ref<Array<{ project_id: string; name: string }>>([]); + +function onChangeTab(n: 1 | 2 | 3) { + active.value = n; + const url = new URL(window.location.href); + url.searchParams.set("tab", String(n)); + window.history.replaceState({}, "", url); +} + +onMounted(async () => { + const url = new URL(window.location.href); + const t = Number(url.searchParams.get("tab")); + if (t === 1 || t === 2 || t === 3) active.value = t as 1 | 2 | 3; + const r = await launcherService.listProjects(); + projects.value = r?.results ?? []; +}); +</script> diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-launch.ts b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-launch.ts new file mode 100644 index 000000000..e5350ed07 --- /dev/null +++ b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-launch.ts @@ -0,0 +1,10 @@ +import { createApp } from "vue"; +import { createPinia } from "pinia"; +import LaunchContainer from "./containers/LaunchContainer.vue"; + +const root = document.getElementById("launch-app"); +if (root) { + const app = createApp(LaunchContainer); + app.use(createPinia()); + app.mount(root); +} diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/components/launch/LaunchContainer.spec.ts b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/components/launch/LaunchContainer.spec.ts new file mode 100644 index 000000000..44d8f5e46 --- /dev/null +++ b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/components/launch/LaunchContainer.spec.ts @@ -0,0 +1,33 @@ +import { mount, flushPromises } from "@vue/test-utils"; +import { createPinia, setActivePinia } from "pinia"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import LaunchContainer from "../../../../js/containers/LaunchContainer.vue"; + +vi.mock("django-airavata-common-ui/js/services/launcherService", () => ({ + launcherService: { + listProjects: vi.fn().mockResolvedValue({ results: [] }), + }, +})); + +describe("LaunchContainer", () => { + beforeEach(() => setActivePinia(createPinia())); + + it("starts on tab 1", async () => { + const w = mount(LaunchContainer); + await flushPromises(); + expect(w.find("[data-test='active-tab']").attributes("data-active")).toBe("1"); + }); + + it("renders the meta header", async () => { + const w = mount(LaunchContainer); + await flushPromises(); + expect(w.find("input[data-test='exp-name']").exists()).toBe(true); + }); + + it("only one tab panel is shown at a time", async () => { + const w = mount(LaunchContainer); + await flushPromises(); + const visiblePanels = w.findAll("[role='tabpanel']").filter((p) => !p.attributes("hidden")); + expect(visiblePanels).toHaveLength(1); + }); +}); diff --git a/airavata-django-portal/django_airavata/apps/workspace/vite.config.js b/airavata-django-portal/django_airavata/apps/workspace/vite.config.js index d0ba14c25..a73d02e6a 100644 --- a/airavata-django-portal/django_airavata/apps/workspace/vite.config.js +++ b/airavata-django-portal/django_airavata/apps/workspace/vite.config.js @@ -25,5 +25,6 @@ export default defineAppConfig({ "compute-detail": resolve(srcDir, "entry-compute-detail.js"), "project-overview": resolve(srcDir, "entry-project-overview.js"), "application-editor": resolve(srcDir, "entry-application-editor.js"), + launch: resolve(srcDir, "entry-launch.ts"), }, });
