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");
+  });
+});

Reply via email to