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 5be9e50237c4075dccd770dcb26e7a24c9f86d67 Author: yasithdev <[email protected]> AuthorDate: Fri Apr 24 22:51:17 2026 -0400 feat(launcher): Tab1ApplicationInputs assembly Replaces the empty stub with the full Tab1 component: AppPicker → InterfacePicker → InputList → OutputList with progressive disclosure. Adds 2 tests; also fixes LaunchContainer mock to stub the new service methods so no unhandled rejections surface from the child component. --- .../js/components/launch/Tab1ApplicationInputs.vue | 46 +++++++++++++++++++++- .../unit/components/launch/LaunchContainer.spec.ts | 5 ++- .../launch/Tab1ApplicationInputs.spec.ts | 43 ++++++++++++++++++++ 3 files changed, 91 insertions(+), 3 deletions(-) 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 index 25a445eed..dc2e24282 100644 --- 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 @@ -1,2 +1,44 @@ -<template><div /></template> -<script setup lang="ts"></script> +<template> + <div> + <section class="mb-3"> + <div class="text-uppercase text-primary small fw-bold mb-1">Application</div> + <AppPicker :applications="apps" /> + </section> + <section v-if="store.pickedApp" class="mb-3"> + <div class="text-uppercase text-primary small fw-bold mb-1">Interface</div> + <InterfacePicker /> + </section> + <section v-if="store.pickedInterface" data-test="inputs-section" class="mb-3"> + <div class="text-uppercase text-primary small fw-bold mb-1">Inputs</div> + <InputList :storages="store.storages" /> + </section> + <section v-if="store.pickedInterface && fileOutputCount > 0" class="mb-3"> + <div class="text-uppercase text-primary small fw-bold mb-1">Outputs</div> + <OutputList :storages="store.storages" /> + </section> + </div> +</template> + +<script setup lang="ts"> +import { computed, onMounted, ref } from "vue"; +import { launcherService } from "django-airavata-common-ui/js/services/launcherService"; +import { useLaunchStore } from "django-airavata-common-ui/js/stores/launch"; +import type { Application } from "django-airavata-common-ui/js/stores/launch-types"; +import AppPicker from "./AppPicker.vue"; +import InterfacePicker from "./InterfacePicker.vue"; +import InputList from "./InputList.vue"; +import OutputList from "./OutputList.vue"; + +const store = useLaunchStore(); +const apps = ref<Application[]>([]); + +onMounted(async () => { + const [a, s] = await Promise.all([launcherService.listApplications(), launcherService.listUserStorages()]); + apps.value = a?.results ?? []; + store.setStorages(s?.results ?? []); +}); + +const fileOutputCount = computed(() => + (store.pickedInterface?.outputs ?? []).filter((o) => o.type === "file" || o.type === "dir").length, +); +</script> 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 index 44d8f5e46..22cced971 100644 --- 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 @@ -5,10 +5,13 @@ import LaunchContainer from "../../../../js/containers/LaunchContainer.vue"; vi.mock("django-airavata-common-ui/js/services/launcherService", () => ({ launcherService: { - listProjects: vi.fn().mockResolvedValue({ results: [] }), + listProjects: vi.fn(), + listApplications: vi.fn(), + listUserStorages: vi.fn(), }, })); + describe("LaunchContainer", () => { beforeEach(() => setActivePinia(createPinia())); diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/components/launch/Tab1ApplicationInputs.spec.ts b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/components/launch/Tab1ApplicationInputs.spec.ts new file mode 100644 index 000000000..b90b43ecb --- /dev/null +++ b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/components/launch/Tab1ApplicationInputs.spec.ts @@ -0,0 +1,43 @@ +import { mount, flushPromises } from "@vue/test-utils"; +import { createPinia, setActivePinia } from "pinia"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import Tab1ApplicationInputs from "../../../../js/components/launch/Tab1ApplicationInputs.vue"; +import { useLaunchStore } from "django-airavata-common-ui/js/stores/launch"; + +vi.mock("django-airavata-common-ui/js/services/launcherService", () => ({ + launcherService: { + listApplications: vi.fn(), + listUserStorages: vi.fn(), + }, +})); + +describe("Tab1ApplicationInputs", () => { + beforeEach(async () => { + setActivePinia(createPinia()); + const { launcherService } = await import( + "django-airavata-common-ui/js/services/launcherService" + ); + vi.mocked(launcherService.listApplications).mockResolvedValue({ + results: [{ + app_id: "namd", name: "NAMD", category: "MD", + content: { kind: "github", url: "g" }, interfaces: [], + }], + }); + vi.mocked(launcherService.listUserStorages).mockResolvedValue({ + results: [{ storage_id: "my-home", name: "My Home", is_primary: true }], + }); + }); + + it("loads applications + storages on mount", async () => { + const w = mount(Tab1ApplicationInputs); + await flushPromises(); + expect(w.findAll("[data-test='app-tile']")).toHaveLength(1); + expect(useLaunchStore().storages).toHaveLength(1); + }); + + it("does not render the inputs section before an app+interface are picked", async () => { + const w = mount(Tab1ApplicationInputs); + await flushPromises(); + expect(w.find("[data-test='inputs-section']").exists()).toBe(false); + }); +});
