Skip to content

Commit f68dcd1

Browse files
authored
Merge pull request #19935 from ahmedhamidawan/show_example_form_data_in_run_form
Show pre-populated landing data values in workflow run form
2 parents a3bd5a0 + f0ebb7b commit f68dcd1

File tree

9 files changed

+320
-76
lines changed

9 files changed

+320
-76
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { mount, type Wrapper } from "@vue/test-utils";
2+
import flushPromises from "flush-promises";
3+
4+
import { SINGULAR_DATA_URI, SINGULAR_FILE_URI, SINGULAR_LIST_URI } from "./testData/uriData";
5+
import { type DataUri, type DataUriCollectionElement, isDataUriCollectionElementCollection } from "./types";
6+
7+
import FormDataUri from "./FormDataUri.vue";
8+
9+
const SELECTORS = {
10+
URI_ELEMENT_FILE: "[data-description='uri element file']",
11+
URI_ELEMENT_COLLECTION: "[data-description='uri element collection']",
12+
URI_LOCATION: "[data-description='uri location']",
13+
URI_IDENTIFIER: "[data-description='uri element identifier']",
14+
URI_ELEMENT_EXT: "[data-description='uri element ext']",
15+
};
16+
17+
function initWrapper(value: DataUri) {
18+
const wrapper = mount(FormDataUri as object, {
19+
propsData: {
20+
value,
21+
},
22+
});
23+
flushPromises();
24+
return wrapper;
25+
}
26+
27+
function assertLocationIsFound(wrapper: Wrapper<Vue>, uriData: DataUri | DataUriCollectionElement) {
28+
if (!("location" in uriData)) {
29+
throw new Error("The DataUri type does not have a url property");
30+
}
31+
32+
expect(wrapper.find(SELECTORS.URI_LOCATION).text()).toBe(uriData.location);
33+
}
34+
35+
function assertIdentifierIsFound(
36+
wrapper: Wrapper<Vue>,
37+
uriData: DataUri | DataUriCollectionElement,
38+
hasIdentifier = false,
39+
expectIdentifier = "File"
40+
) {
41+
if (hasIdentifier) {
42+
if (!("identifier" in uriData)) {
43+
throw new Error("The DataUri type is not a DataUriCollectionElement");
44+
}
45+
expect(wrapper.find(SELECTORS.URI_IDENTIFIER).text()).toBe((uriData as DataUriCollectionElement).identifier);
46+
} else {
47+
expect(wrapper.find(SELECTORS.URI_IDENTIFIER).text()).toBe(expectIdentifier);
48+
}
49+
}
50+
51+
describe("FormDataUri", () => {
52+
it("renders a singular data input correctly", () => {
53+
const wrapper = initWrapper(SINGULAR_DATA_URI);
54+
expect(wrapper.findAll(SELECTORS.URI_ELEMENT_FILE).length).toBe(1);
55+
56+
assertLocationIsFound(wrapper, SINGULAR_DATA_URI);
57+
assertIdentifierIsFound(wrapper, SINGULAR_DATA_URI, false, "Data");
58+
});
59+
60+
it("renders a singular file input correctly", () => {
61+
const wrapper = initWrapper(SINGULAR_FILE_URI);
62+
expect(wrapper.findAll(SELECTORS.URI_ELEMENT_FILE).length).toBe(1);
63+
64+
assertLocationIsFound(wrapper, SINGULAR_FILE_URI);
65+
assertIdentifierIsFound(wrapper, SINGULAR_FILE_URI);
66+
});
67+
68+
it("renders a singular list input correctly", () => {
69+
const testUriDataCollection = SINGULAR_LIST_URI;
70+
71+
const wrapper = initWrapper(testUriDataCollection);
72+
73+
if (!("elements" in testUriDataCollection)) {
74+
throw new Error("The `DataUri` type is not `DataUriCollection`");
75+
}
76+
expect(wrapper.findAll(SELECTORS.URI_ELEMENT_COLLECTION).length).toBe(1);
77+
78+
const collectionElements = wrapper.findAll(SELECTORS.URI_ELEMENT_FILE);
79+
expect(collectionElements.length).toBe(testUriDataCollection.elements.length);
80+
81+
for (let i = 0; i < collectionElements.length; i++) {
82+
const element = collectionElements.at(i);
83+
const expectedElement = testUriDataCollection.elements[i];
84+
if (!expectedElement) {
85+
throw new Error("No element found");
86+
}
87+
88+
expect(isDataUriCollectionElementCollection(expectedElement)).toBe(false);
89+
90+
assertLocationIsFound(element, expectedElement);
91+
92+
assertIdentifierIsFound(element, expectedElement, true);
93+
}
94+
});
95+
});
Lines changed: 29 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,31 @@
1-
<template>
2-
<b-row align-v="center">
3-
<b-col>
4-
<b-form-input :id="id" v-model="displayValue" :class="['ui-input', cls]" :readonly="true" />
5-
</b-col>
6-
</b-row>
7-
</template>
1+
<script setup lang="ts">
2+
import { type DataUri, isDataUriCollection, isDataUriData, isDataUriFile } from "./types";
3+
4+
import FormDataUriElement from "./FormDataUriElement.vue";
85
9-
<script>
10-
export default {
11-
props: {
12-
value: {
13-
type: Object,
14-
},
15-
id: {
16-
type: String,
17-
default: "",
18-
},
19-
multiple: {
20-
type: Boolean,
21-
default: false,
22-
},
23-
cls: {
24-
// Refers to an optional custom css class name
25-
type: String,
26-
default: null,
27-
},
28-
},
29-
computed: {
30-
displayValue: {
31-
get() {
32-
return this.value.url;
33-
},
34-
set(newVal, oldValue) {
35-
if (newVal !== this.value.url) {
36-
this.$emit("input", this.value);
37-
}
38-
},
39-
},
40-
currentValue: {
41-
get() {
42-
const v = this.value ?? "";
43-
if (Array.isArray(v)) {
44-
if (v.length === 0) {
45-
return "";
46-
}
47-
return this.multiple
48-
? this.value.reduce((str_value, v) => str_value + String(v) + "\n", "")
49-
: String(this.value[0]);
50-
}
51-
return String(v);
52-
},
53-
set(newVal, oldVal) {
54-
if (newVal !== oldVal) {
55-
this.$emit("input", newVal);
56-
}
57-
},
58-
},
59-
},
60-
};
6+
const props = defineProps<{
7+
value: DataUri;
8+
}>();
619
</script>
62-
<style scoped>
63-
.ui-input-linked {
64-
border-left-width: 0.5rem;
65-
}
66-
</style>
10+
11+
<template>
12+
<FormDataUriElement
13+
v-if="isDataUriCollection(props.value)"
14+
:value="{
15+
...props.value,
16+
identifier: props.value.name || 'Collection',
17+
}" />
18+
<FormDataUriElement
19+
v-else-if="isDataUriFile(props.value)"
20+
:value="{
21+
...props.value,
22+
identifier: props.value.name || 'File',
23+
}" />
24+
<FormDataUriElement
25+
v-else-if="isDataUriData(props.value)"
26+
:value="{
27+
...props.value,
28+
class: 'File',
29+
identifier: props.value.name || 'Data',
30+
}" />
31+
</template>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<script setup lang="ts">
2+
import { BCollapse, BLink } from "bootstrap-vue";
3+
import { ref } from "vue";
4+
5+
import { type DataUriCollectionElement, isDataUriCollectionElementCollection } from "./types";
6+
7+
const props = defineProps<{
8+
value: DataUriCollectionElement;
9+
}>();
10+
11+
const expanded = ref(false);
12+
</script>
13+
14+
<template>
15+
<div v-if="isDataUriCollectionElementCollection(props.value)" data-description="uri element collection">
16+
<div
17+
tabindex="0"
18+
role="button"
19+
class="form-example-data-element rounded"
20+
@click="expanded = !expanded"
21+
@keydown.enter="expanded = !expanded">
22+
<strong>{{ props.value.identifier || "Collection" }}</strong>
23+
<i v-if="props.value.collection_type">({{ props.value.collection_type }})</i>
24+
<span class="float-right"> {{ expanded ? "Hide" : "Show" }} elements </span>
25+
</div>
26+
<BCollapse :visible="expanded" class="pl-2">
27+
<FormDataUriElement v-for="(element, index) in props.value.elements" :key="index" :value="element" />
28+
</BCollapse>
29+
</div>
30+
<div v-else data-description="uri element file">
31+
<div class="form-example-data-element rounded d-flex justify-content-between align-items-center w-100">
32+
<div class="w-50">
33+
<strong data-description="uri element identifier">{{ props.value.identifier || "Dataset" }}</strong>
34+
<i v-if="props.value.ext" data-description="uri element ext">({{ props.value.ext }})</i>
35+
</div>
36+
<BLink
37+
v-if="props.value.location"
38+
class="location-link w-50"
39+
:href="props.value.location"
40+
target="_blank"
41+
data-description="uri location">
42+
<i>{{ props.value.location }}</i>
43+
</BLink>
44+
</div>
45+
</div>
46+
</template>
47+
48+
<style scoped lang="scss">
49+
.form-example-data-element {
50+
// background-color: $state-success-bg; // TODO: Does not seem to work even
51+
// with the "theme/blue.scss" import
52+
background-color: #c2ebc2;
53+
padding: 0.5rem 1.25rem;
54+
55+
.location-link {
56+
text-align: right;
57+
overflow: hidden;
58+
text-overflow: ellipsis;
59+
white-space: nowrap;
60+
}
61+
}
62+
</style>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type { DataUri } from "../types";
2+
3+
export const SINGULAR_DATA_URI: DataUri = {
4+
src: "url",
5+
location: "link_to_file",
6+
ext: "txt",
7+
};
8+
9+
export const SINGULAR_FILE_URI: DataUri = {
10+
class: "File",
11+
ext: "txt",
12+
location: "link_to_file",
13+
};
14+
15+
export const SINGULAR_LIST_URI: DataUri = {
16+
class: "Collection",
17+
collection_type: "list",
18+
elements: [
19+
{
20+
class: "File",
21+
ext: "txt",
22+
location: "link_to_file",
23+
identifier: "1.bed",
24+
},
25+
{
26+
class: "File",
27+
ext: "txt",
28+
location: "link_to_file",
29+
identifier: "2.bed",
30+
},
31+
{
32+
class: "File",
33+
ext: "txt",
34+
location: "link_to_file",
35+
identifier: "3.bed",
36+
},
37+
],
38+
};

client/src/components/Form/Elements/FormData/types.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,83 @@
1+
/**
2+
* The Uri types here are based on `DataOrCollectionRequest` defined in
3+
* `lib/galaxy/tool_util_models/parameters.py`.
4+
*/
5+
6+
interface DatasetHash {
7+
hash_function: "MD5" | "SHA-1" | "SHA-256" | "SHA-512";
8+
hash_value: string;
9+
}
10+
11+
interface BaseDataUri {
12+
location: string;
13+
name?: string | null;
14+
ext: string;
15+
dbkey?: string;
16+
deferred?: boolean;
17+
created_from_basename?: string | null;
18+
info?: string | null;
19+
hashes?: DatasetHash[] | null;
20+
space_to_tab?: boolean;
21+
to_posix_lines?: boolean;
22+
}
23+
24+
interface DataUriData extends BaseDataUri {
25+
src: "url";
26+
}
27+
28+
interface DataUriFile extends BaseDataUri {
29+
class: "File";
30+
}
31+
32+
export interface DataUriCollectionElementFile extends DataUriFile {
33+
identifier: string;
34+
}
35+
36+
export interface DataUriCollectionElementCollection {
37+
class: "Collection";
38+
identifier: string;
39+
collection_type: string;
40+
elements: DataUriCollectionElement[];
41+
}
42+
43+
interface DataUriCollection {
44+
class: "Collection";
45+
collection_type: string;
46+
elements: DataUriCollectionElement[];
47+
deferred?: boolean;
48+
name?: string | null;
49+
}
50+
51+
export type DataUriCollectionElement = DataUriCollectionElementFile | DataUriCollectionElementCollection;
52+
53+
export type DataUri = DataUriData | DataUriFile | DataUriCollection;
54+
55+
function isBaseDataUri(item: object): item is BaseDataUri {
56+
return Boolean(item) && "location" in item && "ext" in item;
57+
}
58+
59+
export function isDataUriData(item: object): item is DataUriData {
60+
return isBaseDataUri(item) && "src" in item && item.src === "url";
61+
}
62+
63+
export function isDataUriFile(item: object): item is DataUriFile {
64+
return isBaseDataUri(item) && "class" in item && item.class === "File";
65+
}
66+
67+
export function isDataUriCollection(item: object): item is DataUriCollection {
68+
return Boolean(item) && "class" in item && item.class === "Collection" && "elements" in item;
69+
}
70+
71+
export function isDataUri(item: object): item is DataUri {
72+
return Boolean(item) && (isDataUriData(item) || isDataUriFile(item) || isDataUriCollection(item));
73+
}
74+
75+
export function isDataUriCollectionElementCollection(
76+
item: DataUriCollectionElement
77+
): item is DataUriCollectionElementCollection {
78+
return item.class === "Collection";
79+
}
80+
181
export type DataOption = {
282
id: string;
383
hid?: number;

0 commit comments

Comments
 (0)