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 67680b5da7ea056c9f1deb8abe8e9f692a4470ed Author: yasithdev <[email protected]> AuthorDate: Fri Apr 24 22:53:08 2026 -0400 feat(launcher): RuntimeInputs (profile-driven cr/partition/walltime) --- .../js/components/launch/runtime/RuntimeInputs.vue | 80 ++++++++++++++++++++++ .../launch/runtime/RuntimeInputs.spec.ts | 43 ++++++++++++ 2 files changed, 123 insertions(+) diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/runtime/RuntimeInputs.vue b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/runtime/RuntimeInputs.vue new file mode 100644 index 000000000..df1880417 --- /dev/null +++ b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/runtime/RuntimeInputs.vue @@ -0,0 +1,80 @@ +<template> + <div class="row g-2"> + <div class="col-md-3"> + <label class="form-label small">Compute resource</label> + <select + data-test="cr" + class="form-select form-select-sm" + :value="modelValue.compute_resource_id ?? ''" + @change="onCR(($event.target as HTMLSelectElement).value)" + > + <option value="" disabled>Choose…</option> + <option v-for="cr in profile.compute_resources" :key="cr.compute_resource_id" :value="cr.compute_resource_id"> + {{ cr.name }} + </option> + </select> + </div> + <div class="col-md-3"> + <label class="form-label small">Partition</label> + <select + data-test="partition" + class="form-select form-select-sm" + :disabled="!partitions.length" + :value="modelValue.partition ?? ''" + @change="emit('update:modelValue', { ...modelValue, partition: ($event.target as HTMLSelectElement).value })" + > + <option value="" disabled>Choose…</option> + <option v-for="p in partitions" :key="p.name" :value="p.name">{{ p.name }}</option> + </select> + </div> + <div class="col-md-2"> + <label class="form-label small">Walltime</label> + <input + data-test="walltime" + class="form-control form-control-sm" + :value="modelValue.walltime" + @input="emit('update:modelValue', { ...modelValue, walltime: ($event.target as HTMLInputElement).value })" + /> + </div> + <div class="col-md-2"> + <label class="form-label small">Nodes</label> + <input + type="number" min="1" + data-test="nodes" + class="form-control form-control-sm" + :value="modelValue.nodes" + @input="emit('update:modelValue', { ...modelValue, nodes: Number(($event.target as HTMLInputElement).value) })" + /> + </div> + <div class="col-md-2"> + <label class="form-label small">CPUs / node</label> + <input + type="number" min="1" + data-test="cpus" + class="form-control form-control-sm" + :value="modelValue.cpus_per_node" + @input="emit('update:modelValue', { ...modelValue, cpus_per_node: Number(($event.target as HTMLInputElement).value) })" + /> + </div> + </div> +</template> + +<script setup lang="ts"> +import { computed } from "vue"; +import type { ResourceProfile, RuntimeChoice } from "django-airavata-common-ui/js/stores/launch-types"; + +const props = defineProps<{ profile: ResourceProfile; modelValue: RuntimeChoice }>(); +const emit = defineEmits<{ "update:modelValue": [v: RuntimeChoice] }>(); + +const partitions = computed(() => { + if (!props.modelValue.compute_resource_id) return []; + const cr = props.profile.compute_resources.find( + (c) => c.compute_resource_id === props.modelValue.compute_resource_id, + ); + return cr?.partitions ?? []; +}); + +function onCR(id: string) { + emit("update:modelValue", { ...props.modelValue, compute_resource_id: id, partition: null }); +} +</script> diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/components/launch/runtime/RuntimeInputs.spec.ts b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/components/launch/runtime/RuntimeInputs.spec.ts new file mode 100644 index 000000000..e38534a4f --- /dev/null +++ b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/components/launch/runtime/RuntimeInputs.spec.ts @@ -0,0 +1,43 @@ +import { mount } from "@vue/test-utils"; +import { describe, expect, it } from "vitest"; +import RuntimeInputs from "../../../../../js/components/launch/runtime/RuntimeInputs.vue"; + +const PROFILE = { + project_id: "p1", allocation_id: "NSF-1", + compute_resources: [{ + compute_resource_id: "bridges-2", name: "Bridges-2", + mapped_storage: { storage_id: "scratch", scratch_path: "/scratch/p1" }, + partitions: [{ name: "RM", max_walltime: "48:00:00", max_nodes: 64, cpus_per_node: 128 }], + }], +}; + +describe("RuntimeInputs", () => { + it("populates compute resource dropdown from profile", () => { + const w = mount(RuntimeInputs, { + props: { profile: PROFILE, modelValue: { compute_resource_id: null, partition: null, + walltime: "01:00:00", nodes: 1, cpus_per_node: 1 } }, + }); + expect(w.find("select[data-test='cr']").findAll("option").map((o) => o.text())).toContain("Bridges-2"); + }); + + it("emits update:modelValue when the resource changes", async () => { + const w = mount(RuntimeInputs, { + props: { profile: PROFILE, modelValue: { compute_resource_id: null, partition: null, + walltime: "01:00:00", nodes: 1, cpus_per_node: 1 } }, + }); + await w.find("select[data-test='cr']").setValue("bridges-2"); + expect(w.emitted("update:modelValue")?.at(-1)).toEqual([ + expect.objectContaining({ compute_resource_id: "bridges-2", partition: null }), + ]); + }); + + it("limits partition options to the chosen resource", async () => { + const w = mount(RuntimeInputs, { + props: { profile: PROFILE, modelValue: { compute_resource_id: "bridges-2", partition: null, + walltime: "01:00:00", nodes: 1, cpus_per_node: 1 } }, + }); + const opts = w.find("select[data-test='partition']").findAll("option").map((o) => o.text()); + expect(opts).toContain("RM"); + expect(opts).not.toContain("nonexistent"); + }); +});
