Repository: galaxyproject/galaxy
Pull request #19935: Show pre-populated landing data values in workflow run form
Author: login: ahmedhamidawan
Labels: name: kind/enhancement
name: area/UI-UX
Created: 2025-03-31T19:29:04Z | Merged: 2025-05-09T07:46:18Z

https://github.com/user-attachments/assets/83452c71-1f77-4b2c-a088-15f78a3f7c33

## How to test the changes?
(Select all options that apply)
- [ ] I've included appropriate [automated tests](https://docs.galaxyproject.org/en/latest/dev/writing_tests.html).
- [ ] This is a refactoring of components with existing test coverage.
- [ ] Instructions for manual testing are as follows:
  1. [add testing steps and prerequisites here if you didn't write automated tests covering all your changes]

## License
- [x] I agree to license these and all my past contributions to the core galaxy codebase under the [MIT license](https://opensource.org/licenses/MIT).



Diff:
diff --git a/client/src/components/Form/Elements/FormData/FormDataUri.test.ts b/client/src/components/Form/Elements/FormData/FormDataUri.test.ts
new file mode 100644
index 000000000000..a69320016a74
--- /dev/null
+++ b/client/src/components/Form/Elements/FormData/FormDataUri.test.ts
@@ -0,0 +1,95 @@
+import { mount, type Wrapper } from "@vue/test-utils";
+import flushPromises from "flush-promises";
+
+import { SINGULAR_DATA_URI, SINGULAR_FILE_URI, SINGULAR_LIST_URI } from "./testData/uriData";
+import { type DataUri, type DataUriCollectionElement, isDataUriCollectionElementCollection } from "./types";
+
+import FormDataUri from "./FormDataUri.vue";
+
+const SELECTORS = {
+    URI_ELEMENT_FILE: "[data-description='uri element file']",
+    URI_ELEMENT_COLLECTION: "[data-description='uri element collection']",
+    URI_LOCATION: "[data-description='uri location']",
+    URI_IDENTIFIER: "[data-description='uri element identifier']",
+    URI_ELEMENT_EXT: "[data-description='uri element ext']",
+};
+
+function initWrapper(value: DataUri) {
+    const wrapper = mount(FormDataUri as object, {
+        propsData: {
+            value,
+        },
+    });
+    flushPromises();
+    return wrapper;
+}
+
+function assertLocationIsFound(wrapper: Wrapper<Vue>, uriData: DataUri | DataUriCollectionElement) {
+    if (!("location" in uriData)) {
+        throw new Error("The DataUri type does not have a url property");
+    }
+
+    expect(wrapper.find(SELECTORS.URI_LOCATION).text()).toBe(uriData.location);
+}
+
+function assertIdentifierIsFound(
+    wrapper: Wrapper<Vue>,
+    uriData: DataUri | DataUriCollectionElement,
+    hasIdentifier = false,
+    expectIdentifier = "File"
+) {
+    if (hasIdentifier) {
+        if (!("identifier" in uriData)) {
+            throw new Error("The DataUri type is not a DataUriCollectionElement");
+        }
+        expect(wrapper.find(SELECTORS.URI_IDENTIFIER).text()).toBe((uriData as DataUriCollectionElement).identifier);
+    } else {
+        expect(wrapper.find(SELECTORS.URI_IDENTIFIER).text()).toBe(expectIdentifier);
+    }
+}
+
+describe("FormDataUri", () => {
+    it("renders a singular data input correctly", () => {
+        const wrapper = initWrapper(SINGULAR_DATA_URI);
+        expect(wrapper.findAll(SELECTORS.URI_ELEMENT_FILE).length).toBe(1);
+
+        assertLocationIsFound(wrapper, SINGULAR_DATA_URI);
+        assertIdentifierIsFound(wrapper, SINGULAR_DATA_URI, false, "Data");
+    });
+
+    it("renders a singular file input correctly", () => {
+        const wrapper = initWrapper(SINGULAR_FILE_URI);
+        expect(wrapper.findAll(SELECTORS.URI_ELEMENT_FILE).length).toBe(1);
+
+        assertLocationIsFound(wrapper, SINGULAR_FILE_URI);
+        assertIdentifierIsFound(wrapper, SINGULAR_FILE_URI);
+    });
+
+    it("renders a singular list input correctly", () => {
+        const testUriDataCollection = SINGULAR_LIST_URI;
+
+        const wrapper = initWrapper(testUriDataCollection);
+
+        if (!("elements" in testUriDataCollection)) {
+            throw new Error("The `DataUri` type is not `DataUriCollection`");
+        }
+        expect(wrapper.findAll(SELECTORS.URI_ELEMENT_COLLECTION).length).toBe(1);
+
+        const collectionElements = wrapper.findAll(SELECTORS.URI_ELEMENT_FILE);
+        expect(collectionElements.length).toBe(testUriDataCollection.elements.length);
+
+        for (let i = 0; i < collectionElements.length; i++) {
+            const element = collectionElements.at(i);
+            const expectedElement = testUriDataCollection.elements[i];
+            if (!expectedElement) {
+                throw new Error("No element found");
+            }
+
+            expect(isDataUriCollectionElementCollection(expectedElement)).toBe(false);
+
+            assertLocationIsFound(element, expectedElement);
+
+            assertIdentifierIsFound(element, expectedElement, true);
+        }
+    });
+});
diff --git a/client/src/components/Form/Elements/FormData/FormDataUri.vue b/client/src/components/Form/Elements/FormData/FormDataUri.vue
index 0c888fbc4fce..30177e6d6f7a 100644
--- a/client/src/components/Form/Elements/FormData/FormDataUri.vue
+++ b/client/src/components/Form/Elements/FormData/FormDataUri.vue
@@ -1,66 +1,31 @@
-<template>
-    <b-row align-v="center">
-        <b-col>
-            <b-form-input :id="id" v-model="displayValue" :class="['ui-input', cls]" :readonly="true" />
-        </b-col>
-    </b-row>
-</template>
+<script setup lang="ts">
+import { type DataUri, isDataUriCollection, isDataUriData, isDataUriFile } from "./types";
+
+import FormDataUriElement from "./FormDataUriElement.vue";
 
-<script>
-export default {
-    props: {
-        value: {
-            type: Object,
-        },
-        id: {
-            type: String,
-            default: "",
-        },
-        multiple: {
-            type: Boolean,
-            default: false,
-        },
-        cls: {
-            // Refers to an optional custom css class name
-            type: String,
-            default: null,
-        },
-    },
-    computed: {
-        displayValue: {
-            get() {
-                return this.value.url;
-            },
-            set(newVal, oldValue) {
-                if (newVal !== this.value.url) {
-                    this.$emit("input", this.value);
-                }
-            },
-        },
-        currentValue: {
-            get() {
-                const v = this.value ?? "";
-                if (Array.isArray(v)) {
-                    if (v.length === 0) {
-                        return "";
-                    }
-                    return this.multiple
-                        ? this.value.reduce((str_value, v) => str_value + String(v) + "\n", "")
-                        : String(this.value[0]);
-                }
-                return String(v);
-            },
-            set(newVal, oldVal) {
-                if (newVal !== oldVal) {
-                    this.$emit("input", newVal);
-                }
-            },
-        },
-    },
-};
+const props = defineProps<{
+    value: DataUri;
+}>();
 </script>
-<style scoped>
-.ui-input-linked {
-    border-left-width: 0.5rem;
-}
-</style>
+
+<template>
+    <FormDataUriElement
+        v-if="isDataUriCollection(props.value)"
+        :value="{
+            ...props.value,
+            identifier: props.value.name || 'Collection',
+        }" />
+    <FormDataUriElement
+        v-else-if="isDataUriFile(props.value)"
+        :value="{
+            ...props.value,
+            identifier: props.value.name || 'File',
+        }" />
+    <FormDataUriElement
+        v-else-if="isDataUriData(props.value)"
+        :value="{
+            ...props.value,
+            class: 'File',
+            identifier: props.value.name || 'Data',
+        }" />
+</template>
diff --git a/client/src/components/Form/Elements/FormData/FormDataUriElement.vue b/client/src/components/Form/Elements/FormData/FormDataUriElement.vue
new file mode 100644
index 000000000000..2e50504d11c7
--- /dev/null
+++ b/client/src/components/Form/Elements/FormData/FormDataUriElement.vue
@@ -0,0 +1,62 @@
+<script setup lang="ts">
+import { BCollapse, BLink } from "bootstrap-vue";
+import { ref } from "vue";
+
+import { type DataUriCollectionElement, isDataUriCollectionElementCollection } from "./types";
+
+const props = defineProps<{
+    value: DataUriCollectionElement;
+}>();
+
+const expanded = ref(false);
+</script>
+
+<template>
+    <div v-if="isDataUriCollectionElementCollection(props.value)" data-description="uri element collection">
+        <div
+            tabindex="0"
+            role="button"
+            class="form-example-data-element rounded"
+            @click="expanded = !expanded"
+            @keydown.enter="expanded = !expanded">
+            <strong>{{ props.value.identifier || "Collection" }}</strong>
+            <i v-if="props.value.collection_type">({{ props.value.collection_type }})</i>
+            <span class="float-right"> {{ expanded ? "Hide" : "Show" }} elements </span>
+        </div>
+        <BCollapse :visible="expanded" class="pl-2">
+            <FormDataUriElement v-for="(element, index) in props.value.elements" :key="index" :value="element" />
+        </BCollapse>
+    </div>
+    <div v-else data-description="uri element file">
+        <div class="form-example-data-element rounded d-flex justify-content-between align-items-center w-100">
+            <div class="w-50">
+                <strong data-description="uri element identifier">{{ props.value.identifier || "Dataset" }}</strong>
+                <i v-if="props.value.ext" data-description="uri element ext">({{ props.value.ext }})</i>
+            </div>
+            <BLink
+                v-if="props.value.location"
+                class="location-link w-50"
+                :href="props.value.location"
+                target="_blank"
+                data-description="uri location">
+                <i>{{ props.value.location }}</i>
+            </BLink>
+        </div>
+    </div>
+</template>
+
+<style scoped lang="scss">
+.form-example-data-element {
+    // background-color: $state-success-bg; // TODO: Does not seem to work even
+    //                                              with the "theme/blue.scss" import
+    background-color: #c2ebc2;
+    padding: 0.5rem 1.25rem;
+
+    .location-link {
+        text-align: right;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+    }
+}
+</style>
diff --git a/client/src/components/Form/Elements/FormData/testData/uriData.ts b/client/src/components/Form/Elements/FormData/testData/uriData.ts
new file mode 100644
index 000000000000..13c393cbd073
--- /dev/null
+++ b/client/src/components/Form/Elements/FormData/testData/uriData.ts
@@ -0,0 +1,38 @@
+import type { DataUri } from "../types";
+
+export const SINGULAR_DATA_URI: DataUri = {
+    src: "url",
+    location: "link_to_file",
+    ext: "txt",
+};
+
+export const SINGULAR_FILE_URI: DataUri = {
+    class: "File",
+    ext: "txt",
+    location: "link_to_file",
+};
+
+export const SINGULAR_LIST_URI: DataUri = {
+    class: "Collection",
+    collection_type: "list",
+    elements: [
+        {
+            class: "File",
+            ext: "txt",
+            location: "link_to_file",
+            identifier: "1.bed",
+        },
+        {
+            class: "File",
+            ext: "txt",
+            location: "link_to_file",
+            identifier: "2.bed",
+        },
+        {
+            class: "File",
+            ext: "txt",
+            location: "link_to_file",
+            identifier: "3.bed",
+        },
+    ],
+};
diff --git a/client/src/components/Form/Elements/FormData/types.ts b/client/src/components/Form/Elements/FormData/types.ts
index 4daa5467f333..18789ed40ab7 100644
--- a/client/src/components/Form/Elements/FormData/types.ts
+++ b/client/src/components/Form/Elements/FormData/types.ts
@@ -1,3 +1,83 @@
+/**
+ * The Uri types here are based on `DataOrCollectionRequest` defined in
+ * `lib/galaxy/tool_util_models/parameters.py`.
+ */
+
+interface DatasetHash {
+    hash_function: "MD5" | "SHA-1" | "SHA-256" | "SHA-512";
+    hash_value: string;
+}
+
+interface BaseDataUri {
+    location: string;
+    name?: string | null;
+    ext: string;
+    dbkey?: string;
+    deferred?: boolean;
+    created_from_basename?: string | null;
+    info?: string | null;
+    hashes?: DatasetHash[] | null;
+    space_to_tab?: boolean;
+    to_posix_lines?: boolean;
+}
+
+interface DataUriData extends BaseDataUri {
+    src: "url";
+}
+
+interface DataUriFile extends BaseDataUri {
+    class: "File";
+}
+
+export interface DataUriCollectionElementFile extends DataUriFile {
+    identifier: string;
+}
+
+export interface DataUriCollectionElementCollection {
+    class: "Collection";
+    identifier: string;
+    collection_type: string;
+    elements: DataUriCollectionElement[];
+}
+
+interface DataUriCollection {
+    class: "Collection";
+    collection_type: string;
+    elements: DataUriCollectionElement[];
+    deferred?: boolean;
+    name?: string | null;
+}
+
+export type DataUriCollectionElement = DataUriCollectionElementFile | DataUriCollectionElementCollection;
+
+export type DataUri = DataUriData | DataUriFile | DataUriCollection;
+
+function isBaseDataUri(item: object): item is BaseDataUri {
+    return Boolean(item) && "location" in item && "ext" in item;
+}
+
+export function isDataUriData(item: object): item is DataUriData {
+    return isBaseDataUri(item) && "src" in item && item.src === "url";
+}
+
+export function isDataUriFile(item: object): item is DataUriFile {
+    return isBaseDataUri(item) && "class" in item && item.class === "File";
+}
+
+export function isDataUriCollection(item: object): item is DataUriCollection {
+    return Boolean(item) && "class" in item && item.class === "Collection" && "elements" in item;
+}
+
+export function isDataUri(item: object): item is DataUri {
+    return Boolean(item) && (isDataUriData(item) || isDataUriFile(item) || isDataUriCollection(item));
+}
+
+export function isDataUriCollectionElementCollection(
+    item: DataUriCollectionElement
+): item is DataUriCollectionElementCollection {
+    return item.class === "Collection";
+}
+
 export type DataOption = {
     id: string;
     hid?: number;
diff --git a/client/src/components/Form/FormElement.vue b/client/src/components/Form/FormElement.vue
index 7d7d4ebe348d..371b459e0969 100644
--- a/client/src/components/Form/FormElement.vue
+++ b/client/src/components/Form/FormElement.vue
@@ -9,6 +9,7 @@ import { computed, ref, useAttrs } from "vue";
 
 import { linkify } from "@/utils/utils";
 
+import { isDataUri } from "./Elements/FormData/types";
 import type { FormParameterAttributes, FormParameterTypes, FormParameterValue } from "./parameterTypes";
 
 import FormBoolean from "./Elements/FormBoolean.vue";
@@ -168,14 +169,7 @@ const showField = computed(() => !collapsed.value && !props.disabled);
 const formDataField = computed(() =>
     props.type && ["data", "data_collection"].includes(props.type) ? (props.type as "data" | "data_collection") : null
 );
-const isUriDataField = computed(() => {
-    const dataField = props.type == "data";
-    if (dataField && props.value && "src" in props.value) {
-        const src = props.value.src;
-        return src == "url";
-    }
-    return false;
-});
+const isUriDataField = computed(() => formDataField.value && isDataUri(props.value));
 
 const previewText = computed(() => attrs.value["text_value"]);
 const helpText = computed(() => {
@@ -422,7 +416,7 @@ function onAlert(value: string | undefined) {
                 <FormDataUri
                     v-else-if="isUriDataField"
                     :id="props.id"
-                    v-model="currentValue"
+                    :value="attrs.value"
                     :multiple="attrs.multiple" />
                 <FormData
                     v-else-if="formDataField"
diff --git a/client/src/components/Landing/WorkflowLanding.vue b/client/src/components/Landing/WorkflowLanding.vue
index ddc99ad23e73..d6ec0fae71c6 100644
--- a/client/src/components/Landing/WorkflowLanding.vue
+++ b/client/src/components/Landing/WorkflowLanding.vue
@@ -41,7 +41,7 @@ claimWorkflow(props.uuid, props.public, props.secret).then(() => {
         <div v-else-if="!claimState.workflowId">
             <LoadingSpan message="Loading workflow parameters" />
         </div>
-        <div v-else>
+        <div v-else class="h-100">
             <WorkflowRun
                 :workflow-id="claimState.workflowId"
                 :prefer-simple-form="true"
diff --git a/client/src/components/Workflow/Run/WorkflowRunGraph.vue b/client/src/components/Workflow/Run/WorkflowRunGraph.vue
index 50cb64fd5ca5..f44507ce1d40 100644
--- a/client/src/components/Workflow/Run/WorkflowRunGraph.vue
+++ b/client/src/components/Workflow/Run/WorkflowRunGraph.vue
@@ -39,6 +39,7 @@ const { steps, loading, loadWorkflowOntoGraph } = useWorkflowRunGraph(
 );
 
 try {
+    datatypesMapperStore.createMapper();
     loadWorkflowOntoGraph();
 } catch (error) {
     errorMessage.value = errorMessageAsString(error);
@@ -66,6 +67,7 @@ try {
                 readonly />
         </BCard>
     </div>
+    <BAlert v-else variant="danger" show> Unable to load graph due to missing datatypes mapper. </BAlert>
 </template>
 
 <style scoped>
diff --git a/client/src/composables/useWorkflowRunGraph.ts b/client/src/composables/useWorkflowRunGraph.ts
index f10b097cb388..8d5b28fa44ed 100644
--- a/client/src/composables/useWorkflowRunGraph.ts
+++ b/client/src/composables/useWorkflowRunGraph.ts
@@ -1,6 +1,7 @@
 import { faCheckCircle, faExclamationCircle, faSpinner, type IconDefinition } from "@fortawesome/free-solid-svg-icons";
 import { computed, type Ref, ref } from "vue";
 
+import { type DataUri, isDataUri, isDataUriCollection } from "@/components/Form/Elements/FormData/types";
 import { isWorkflowInput } from "@/components/Workflow/constants";
 import { fromSimple } from "@/components/Workflow/Editor/modules/model";
 import { getWorkflowFull } from "@/components/Workflow/workflows.services";
@@ -28,7 +29,7 @@ interface BaseDataToolParameterInput {
 }
 interface DataToolParameterInput extends BaseDataToolParameterInput {}
 interface DataCollectionToolParameterInput extends BaseDataToolParameterInput {}
-export type DataInput = DataToolParameterInput | DataCollectionToolParameterInput | boolean | string | null;
+export type DataInput = DataToolParameterInput | DataCollectionToolParameterInput | boolean | string | DataUri | null;
 
 interface WorkflowRunStepInfo {
     headerClass?: Record<string, boolean>;
@@ -126,7 +127,14 @@ export function useWorkflowRunGraph(
         if (modelClass === "BooleanToolParameter") {
             return getStepDescription(dataInput as boolean, true);
         } else if (modelClass === "DataToolParameter" || modelClass === "DataCollectionToolParameter") {
-            dataInput = dataInput as DataToolParameterInput | DataCollectionToolParameterInput;
+            if (dataInput && typeof dataInput === "object" && isDataUri(dataInput)) {
+                return getStepDescription(
+                    dataInput.name || (isDataUriCollection(dataInput) ? "Collection provided" : "File provided"),
+                    true
+                );
+            } else {
+                dataInput = dataInput as DataToolParameterInput | DataCollectionToolParameterInput;
+            }
             const inputVals = dataInput?.values;
             const options = formInput?.options;
 


Reviews:
user: login: mvdbeek
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T19:38:54Z
user: login: ahmedhamidawan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T19:48:13Z
user: login: mvdbeek
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T16:22:10Z
user: login: ahmedhamidawan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T16:24:11Z
user: login: mvdbeek
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T16:36:34Z
user: login: ahmedhamidawan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T16:40:13Z
user: login: mvdbeek
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T16:46:11Z
user: login: ahmedhamidawan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T16:47:36Z
user: login: mvdbeek
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T16:48:48Z
user: login: ahmedhamidawan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T21:50:22Z
user: login: mvdbeek
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T13:36:29Z
user: login: mvdbeek
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T13:36:58Z
user: login: ahmedhamidawan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T13:39:49Z

Review comments:
diff_hunk: @@ -0,0 +1,28 @@
+<script setup lang="ts">
+import type { ExampleData } from "./types";
+
+import FormExampleDataElement from "./FormExampleDataElement.vue";
+
+const props = defineProps<{
+    value: ExampleData;
+}>();
+</script>
+
+<template>
+    <FormExampleDataElement
+        v-if="props.value.class === 'Collection'"
path: client/src/components/Form/Elements/FormData/FormExampleData.vue
user: login: mvdbeek
body: All types of parameters can be specified in a landing request. While you're at it, please don't call it example data, that's just one application.
created_at: 2025-03-31T19:38:54Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,28 @@
+<script setup lang="ts">
+import type { ExampleData } from "./types";
+
+import FormExampleDataElement from "./FormExampleDataElement.vue";
+
+const props = defineProps<{
+    value: ExampleData;
+}>();
+</script>
+
+<template>
+    <FormExampleDataElement
+        v-if="props.value.class === 'Collection'"
path: client/src/components/Form/Elements/FormData/FormExampleData.vue
user: login: ahmedhamidawan
body: Yes, this is a really rough draft (especially the types I've added). I was trying to look locally for a schema of some sort for all examples of landing data.

Will polish this a lot more once I get back to this
created_at: 2025-03-31T19:48:13Z
author_association: MEMBER
diff_hunk: @@ -1,3 +1,94 @@
+/**
+ * The Uri types here are based on `DataOrCollectionRequest` defined in
+ * `lib/galaxy/tool_util_models/parameters.py`.
+ */
+
+type BaseDataUri = {
+    // Must have one of these 2
+    url?: string;
+    location?: string; // alias for `url`
+
+    extension?: string;
+    filetype?: string; // alias for `ext`)
+    ext?: string;
+
+    name?: string;
+};
+
+type DataUriData = BaseDataUri & {
+    src: "url";
+};
+
+type DataUriFile = BaseDataUri & {
+    // Must have one of these 2
+    class?: "File";
+    class_?: "File"; // alias for `class`
path: client/src/components/Form/Elements/FormData/types.ts
user: login: mvdbeek
body: That's definitely not right, `class_` isn't public. I would suggest generating the typescript types from the pydantic models.
created_at: 2025-05-07T16:22:09Z
author_association: MEMBER
diff_hunk: @@ -1,3 +1,94 @@
+/**
+ * The Uri types here are based on `DataOrCollectionRequest` defined in
+ * `lib/galaxy/tool_util_models/parameters.py`.
+ */
+
+type BaseDataUri = {
+    // Must have one of these 2
+    url?: string;
+    location?: string; // alias for `url`
+
+    extension?: string;
+    filetype?: string; // alias for `ext`)
+    ext?: string;
+
+    name?: string;
+};
+
+type DataUriData = BaseDataUri & {
+    src: "url";
+};
+
+type DataUriFile = BaseDataUri & {
+    // Must have one of these 2
+    class?: "File";
+    class_?: "File"; // alias for `class`
path: client/src/components/Form/Elements/FormData/types.ts
user: login: ahmedhamidawan
body: Yes, this confused me on how to type aliases on the client-end.

> generating the typescript types from the pydantic models

You mean kind of how the client api schema is built?
created_at: 2025-05-07T16:24:11Z
author_association: MEMBER
diff_hunk: @@ -1,3 +1,94 @@
+/**
+ * The Uri types here are based on `DataOrCollectionRequest` defined in
+ * `lib/galaxy/tool_util_models/parameters.py`.
+ */
+
+type BaseDataUri = {
+    // Must have one of these 2
+    url?: string;
+    location?: string; // alias for `url`
+
+    extension?: string;
+    filetype?: string; // alias for `ext`)
+    ext?: string;
+
+    name?: string;
+};
+
+type DataUriData = BaseDataUri & {
+    src: "url";
+};
+
+type DataUriFile = BaseDataUri & {
+    // Must have one of these 2
+    class?: "File";
+    class_?: "File"; // alias for `class`
path: client/src/components/Form/Elements/FormData/types.ts
user: login: mvdbeek
body: Yes, lots of ways to do this. This would be one way:
```python
import json
from galaxy.tool_util_models.parameters import DataOrCollectionRequestAdapter


with open("schema.json", "w") as out:
    out.write(json.dumps(DataOrCollectionRequestAdapter.json_schema()))
```

then `npx --package=json-schema-to-typescript json2ts < schema.json` gives you

```typescript
/* eslint-disable */
/**
 * This file was automatically generated by json-schema-to-typescript.
 * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
 * and run json-schema-to-typescript to regenerate this file.
 */

export type Src = "hda";
export type Id = string;
export type Src1 = "ldda";
export type Id1 = string;
/**
 */
export type Src2 = "ld";
export type Id2 = string;
export type Location = string;
export type Name = string | null;
export type Ext = string;
export type Dbkey = string;
export type Deferred = boolean;
export type CreatedFromBasename = string | null;
export type Info = string | null;
export type Hashes = DatasetHash[] | null;
export type HashFunction = "MD5" | "SHA-1" | "SHA-256" | "SHA-512";
export type HashValue = string;
export type SpaceToTab = boolean;
export type ToPosixLines = boolean;
export type Src3 = "url";
export type Location1 = string;
export type Name1 = string | null;
export type Ext1 = string;
export type Dbkey1 = string;
export type Deferred1 = boolean;
export type CreatedFromBasename1 = string | null;
export type Info1 = string | null;
export type Hashes1 = DatasetHash[] | null;
export type SpaceToTab1 = boolean;
export type ToPosixLines1 = boolean;
export type Class = "File";
export type Src4 = null;
export type Class1 = "Collection";
export type CollectionType = string;
export type Class2 = "Collection";
export type Identifier = string;
export type CollectionType1 = string;
export type Location2 = string;
export type Name2 = string | null;
export type Ext2 = string;
export type Dbkey2 = string;
export type Deferred2 = boolean;
export type CreatedFromBasename2 = string | null;
export type Info2 = string | null;
export type Hashes2 = DatasetHash[] | null;
export type SpaceToTab2 = boolean;
export type ToPosixLines2 = boolean;
export type Class3 = "File";
export type Src5 = null;
export type Identifier1 = string;
export type Elements1 = (CollectionElementCollectionRequestUri | CollectionElementDataRequestUri)[];
export type Elements = (CollectionElementCollectionRequestUri | CollectionElementDataRequestUri)[];
export type Deferred3 = boolean;
export type Name3 = string | null;
export type Src6 = null;
export type Src7 = "hdca";
export type Id3 = string;

export interface DataRequestHda {
  src?: Src;
  id: Id;
}
export interface DataRequestLdda {
  src?: Src1;
  id: Id1;
}
export interface DataRequestLd {
  src: Src2;
  id: Id2;
}
export interface DataRequestUri {
  location: Location;
  name?: Name;
  ext: Ext;
  dbkey?: Dbkey;
  deferred?: Deferred;
  created_from_basename?: CreatedFromBasename;
  info?: Info;
  hashes?: Hashes;
  space_to_tab?: SpaceToTab;
  to_posix_lines?: ToPosixLines;
  src?: Src3;
}
export interface DatasetHash {
  hash_function: HashFunction;
  hash_value: HashValue;
}
export interface FileRequestUri {
  location: Location1;
  name?: Name1;
  ext: Ext1;
  dbkey?: Dbkey1;
  deferred?: Deferred1;
  created_from_basename?: CreatedFromBasename1;
  info?: Info1;
  hashes?: Hashes1;
  space_to_tab?: SpaceToTab1;
  to_posix_lines?: ToPosixLines1;
  class: Class;
  src?: Src4;
}
export interface DataRequestCollectionUri {
  class: Class1;
  collection_type: CollectionType;
  elements: Elements;
  deferred?: Deferred3;
  name?: Name3;
  src?: Src6;
}
export interface CollectionElementCollectionRequestUri {
  class: Class2;
  identifier: Identifier;
  collection_type: CollectionType1;
  elements: Elements1;
}
export interface CollectionElementDataRequestUri {
  location: Location2;
  name?: Name2;
  ext: Ext2;
  dbkey?: Dbkey2;
  deferred?: Deferred2;
  created_from_basename?: CreatedFromBasename2;
  info?: Info2;
  hashes?: Hashes2;
  space_to_tab?: SpaceToTab2;
  to_posix_lines?: ToPosixLines2;
  class: Class3;
  src?: Src5;
  identifier: Identifier1;
}
export interface DataRequestHdca {
  src?: Src7;
  id: Id3;
}
```

For what you need now I think you can just cross-check against this ? I don't think we need fully auto-generated types here, just the `class_` check isn't necessary.
created_at: 2025-05-07T16:36:34Z
author_association: MEMBER
diff_hunk: @@ -1,3 +1,94 @@
+/**
+ * The Uri types here are based on `DataOrCollectionRequest` defined in
+ * `lib/galaxy/tool_util_models/parameters.py`.
+ */
+
+type BaseDataUri = {
+    // Must have one of these 2
+    url?: string;
+    location?: string; // alias for `url`
+
+    extension?: string;
+    filetype?: string; // alias for `ext`)
+    ext?: string;
+
+    name?: string;
+};
+
+type DataUriData = BaseDataUri & {
+    src: "url";
+};
+
+type DataUriFile = BaseDataUri & {
+    // Must have one of these 2
+    class?: "File";
+    class_?: "File"; // alias for `class`
path: client/src/components/Form/Elements/FormData/types.ts
user: login: ahmedhamidawan
body: Thanks, this is good to learn.

So you mean I can keep everything else as is (**after cross checking against this and checking if it all matches up**) and just remove the check for `class_`?
created_at: 2025-05-07T16:40:13Z
author_association: MEMBER
diff_hunk: @@ -1,3 +1,94 @@
+/**
+ * The Uri types here are based on `DataOrCollectionRequest` defined in
+ * `lib/galaxy/tool_util_models/parameters.py`.
+ */
+
+type BaseDataUri = {
+    // Must have one of these 2
+    url?: string;
+    location?: string; // alias for `url`
+
+    extension?: string;
+    filetype?: string; // alias for `ext`)
+    ext?: string;
+
+    name?: string;
+};
+
+type DataUriData = BaseDataUri & {
+    src: "url";
+};
+
+type DataUriFile = BaseDataUri & {
+    // Must have one of these 2
+    class?: "File";
+    class_?: "File"; // alias for `class`
path: client/src/components/Form/Elements/FormData/types.ts
user: login: mvdbeek
body: There's other inconsistencies as well. It should only be `ext`, not filetype is what I see directly. `https://pypi.org/project/pydantic-to-typescript/` gives a bit more readable output.

```typescript
export interface BaseDataRequest {
  location: string;
  name?: string | null;
  ext: string;
  dbkey?: string;
  deferred?: boolean;
  created_from_basename?: string | null;
  info?: string | null;
  hashes?: DatasetHash[] | null;
  space_to_tab?: boolean;
  to_posix_lines?: boolean;
}
export interface DatasetHash {
  hash_function: "MD5" | "SHA-1" | "SHA-256" | "SHA-512";
  hash_value: string;
}

export interface CollectionElementCollectionRequestUri {
  class: "Collection";
  identifier: string;
  collection_type: string;
  elements: (CollectionElementCollectionRequestUri | CollectionElementDataRequestUri)[];
}
export interface CollectionElementDataRequestUri {
  location: string;
  name?: string | null;
  ext: string;
  dbkey?: string;
  deferred?: boolean;
  created_from_basename?: string | null;
  info?: string | null;
  hashes?: DatasetHash[] | null;
  space_to_tab?: boolean;
  to_posix_lines?: boolean;
  class: "File";
  identifier: string;
}
export interface DataRequestCollectionUri {
  class: "Collection";
  collection_type: string;
  elements: (CollectionElementCollectionRequestUri | CollectionElementDataRequestUri)[];
  deferred?: boolean;
  name?: string | null;
}

export interface DataRequestUri {
  location: string;
  name?: string | null;
  ext: string;
  dbkey?: string;
  deferred?: boolean;
  created_from_basename?: string | null;
  info?: string | null;
  hashes?: DatasetHash[] | null;
  space_to_tab?: boolean;
  to_posix_lines?: boolean;
  src?: "url";
}
export interface FileRequestUri {
  location: string;
  name?: string | null;
  ext: string;
  dbkey?: string;
  deferred?: boolean;
  created_from_basename?: string | null;
  info?: string | null;
  hashes?: DatasetHash[] | null;
  space_to_tab?: boolean;
  to_posix_lines?: boolean;
  class: "File";
}
```

are the relevant models I think
created_at: 2025-05-07T16:46:11Z
author_association: MEMBER
diff_hunk: @@ -1,3 +1,94 @@
+/**
+ * The Uri types here are based on `DataOrCollectionRequest` defined in
+ * `lib/galaxy/tool_util_models/parameters.py`.
+ */
+
+type BaseDataUri = {
+    // Must have one of these 2
+    url?: string;
+    location?: string; // alias for `url`
+
+    extension?: string;
+    filetype?: string; // alias for `ext`)
+    ext?: string;
+
+    name?: string;
+};
+
+type DataUriData = BaseDataUri & {
+    src: "url";
+};
+
+type DataUriFile = BaseDataUri & {
+    // Must have one of these 2
+    class?: "File";
+    class_?: "File"; // alias for `class`
path: client/src/components/Form/Elements/FormData/types.ts
user: login: ahmedhamidawan
body: Ok, thank you, I'll replicate this there and adjust them accordingly
created_at: 2025-05-07T16:47:36Z
author_association: MEMBER
diff_hunk: @@ -1,3 +1,94 @@
+/**
+ * The Uri types here are based on `DataOrCollectionRequest` defined in
+ * `lib/galaxy/tool_util_models/parameters.py`.
+ */
+
+type BaseDataUri = {
+    // Must have one of these 2
+    url?: string;
+    location?: string; // alias for `url`
+
+    extension?: string;
+    filetype?: string; // alias for `ext`)
+    ext?: string;
+
+    name?: string;
+};
+
+type DataUriData = BaseDataUri & {
+    src: "url";
+};
+
+type DataUriFile = BaseDataUri & {
+    // Must have one of these 2
+    class?: "File";
+    class_?: "File"; // alias for `class`
+};
+
+export type DataUriCollectionElementFile = DataUriFile & {
+    identifier: string;
+};
+
+export type DataUriCollectionElementCollection = {
+    // Must have one of these 2
+    class?: "Collection";
+    class_?: "Collection"; // alias for `class`
+
+    identifier: string;
+
+    type?: string;
path: client/src/components/Form/Elements/FormData/types.ts
user: login: mvdbeek
body: It should also only be collection_type
```suggestion
```
created_at: 2025-05-07T16:48:47Z
author_association: MEMBER
diff_hunk: @@ -1,3 +1,94 @@
+/**
+ * The Uri types here are based on `DataOrCollectionRequest` defined in
+ * `lib/galaxy/tool_util_models/parameters.py`.
+ */
+
+type BaseDataUri = {
+    // Must have one of these 2
+    url?: string;
+    location?: string; // alias for `url`
+
+    extension?: string;
+    filetype?: string; // alias for `ext`)
+    ext?: string;
+
+    name?: string;
+};
+
+type DataUriData = BaseDataUri & {
+    src: "url";
+};
+
+type DataUriFile = BaseDataUri & {
+    // Must have one of these 2
+    class?: "File";
+    class_?: "File"; // alias for `class`
path: client/src/components/Form/Elements/FormData/types.ts
user: login: ahmedhamidawan
body: I've made the changes here https://github.com/galaxyproject/galaxy/pull/19935/commits/1913be606ad3cdf0c03f4e55920c5239caccb29a , and also added an explanation in the commit message.
created_at: 2025-05-07T21:50:22Z
author_association: MEMBER
diff_hunk: @@ -39,6 +39,7 @@ const { steps, loading, loadWorkflowOntoGraph } = useWorkflowRunGraph(
 );
 
 try {
+    datatypesMapperStore.createMapper();
path: client/src/components/Workflow/Run/WorkflowRunGraph.vue
user: login: mvdbeek
body: This is redundant with using the store and should have no effect.
created_at: 2025-05-08T13:36:28Z
author_association: MEMBER
diff_hunk: @@ -66,6 +67,7 @@ try {
                 readonly />
         </BCard>
     </div>
+    <BAlert v-else variant="danger" show> Unable to load graph due to missing datatypes mapper. </BAlert>
path: client/src/components/Workflow/Run/WorkflowRunGraph.vue
user: login: mvdbeek
body: Is this actually reachable ? If it is there must be an actual error message somewhere.
created_at: 2025-05-08T13:36:58Z
author_association: MEMBER
diff_hunk: @@ -66,6 +67,7 @@ try {
                 readonly />
         </BCard>
     </div>
+    <BAlert v-else variant="danger" show> Unable to load graph due to missing datatypes mapper. </BAlert>
path: client/src/components/Workflow/Run/WorkflowRunGraph.vue
user: login: ahmedhamidawan
body: This somehow happened only in the case of workflow landings and trying to load the graph then. Let me take another look and try to find where it originates...
created_at: 2025-05-08T13:39:49Z
author_association: MEMBER

PR comments:
user: login: ahmedhamidawan
created_at: 2025-04-01T01:31:14Z
body: Hmm interestingly I hadn't looked but a version of this interface exists already (to some extent):

![image](https://github.com/user-attachments/assets/49bffa32-36f5-4c5c-bf0f-95717af156aa)

This is what you see when you land from BRC Analytics for e.g., I haven't fully investigated why this is different from a workflow landing from the IWC site for example:

![image](https://github.com/user-attachments/assets/40993eb1-bd0e-41a6-abe9-11e3af9f584a)

Will take the existing view into consideration now...
author_association: MEMBER
user: login: ahmedhamidawan
created_at: 2025-05-05T16:44:01Z
body: > ... I haven't fully investigated why this is different from a workflow landing...

I mean, I do see that this difference comes from the 2 different workflow landing responses:

| BRC Analytics | IWC Site |
| ---- | ---- |
| ![image](https://github.com/user-attachments/assets/11ad80ff-85d8-4832-afa7-de5ae65c8407) | ![image](https://github.com/user-attachments/assets/a22ee762-1377-46c6-9f34-a387a155e731) |

and the front end expects `src === "url"` and checks the `url` key; and based on that renders the `FormDataUri` component.

I understand that the backend from then on (once you run) handles all types of parameters, and hence, when you run (on Test), the workflow runs for either workflow landing structure.

But what confuses me is that when I run something of the second (IWC) type response _locally_, I get this:

![image](https://github.com/user-attachments/assets/e3f82079-5a0c-4237-bd27-0462e2891c0a)

which comes from here: 
https://github.com/galaxyproject/galaxy/blob/37a26a6926cf3cd6711e0acc89be2738161141c7/lib/galaxy/workflow/run_request.py#L386-L389

Can someone explain where in the backend we actually accept all types of request states (especially for collections)?
author_association: MEMBER
user: login: mvdbeek
created_at: 2025-05-05T17:18:37Z
body: It's cause that's not merged yet, https://github.com/galaxyproject/galaxy/pull/20004
author_association: MEMBER
user: login: ahmedhamidawan
created_at: 2025-05-05T17:22:59Z
body: > It's cause that's not merged yet, #20004

Ah ok 😅 

I can rebase and see what we can do here...
author_association: MEMBER
user: login: mvdbeek
created_at: 2025-05-08T13:33:40Z
body: Those selenium errors look related ?
author_association: MEMBER