Skip to content

Commit

Permalink
Merge branch 'release_24.1' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
mvdbeek committed Sep 4, 2024
2 parents 55a9c22 + 478e989 commit 9a231f7
Show file tree
Hide file tree
Showing 14 changed files with 115 additions and 23 deletions.
7 changes: 6 additions & 1 deletion client/src/api/schema/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12568,7 +12568,12 @@ export interface components {
* Value
* @description The values of the job parameter
*/
value?: components["schemas"]["EncodedJobParameterHistoryItem"][] | number | boolean | string | null;
value?:
| (components["schemas"]["EncodedJobParameterHistoryItem"] | null)[]
| number
| boolean
| string
| null;
};
/**
* JobSourceType
Expand Down
29 changes: 23 additions & 6 deletions client/src/components/Collections/common/CollectionEditView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,26 @@ const collectionChangeKey = ref(0);
const attributesData = computed(() => {
return collectionAttributesStore.getAttributes(props.collectionId);
});
const attributesLoadError = computed(() =>
errorMessageAsString(collectionAttributesStore.hasItemLoadError(props.collectionId))
);
const collection = computed(() => {
return collectionStore.getCollectionById(props.collectionId);
});
const collectionLoadError = computed(() => {
if (collection.value) {
return errorMessageAsString(collectionStore.hasLoadingCollectionElementsError(collection.value));
}
return "";
});
watch([attributesLoadError, collectionLoadError], () => {
if (attributesLoadError.value) {
errorMessage.value = attributesLoadError.value;
} else if (collectionLoadError.value) {
errorMessage.value = collectionLoadError.value;
}
});
const databaseKeyFromElements = computed(() => {
return attributesData.value?.dbkey;
});
Expand Down Expand Up @@ -101,7 +118,7 @@ async function clickedSave(attribute: string, newValue: any) {
body: { dbkey: dbKey },
});
if (error) {
errorMessage.value = errorMessageAsString(error, "History import failed.");
errorMessage.value = errorMessageAsString(error, `Changing ${attribute} failed.`);
}
}
Expand All @@ -119,7 +136,7 @@ async function clickedConvert(selectedConverter: any) {
await axios.post(url, data).catch(handleError);
successMessage.value = "Conversion started successfully.";
} catch (err) {
errorMessage.value = errorMessageAsString(err, "History import failed.");
errorMessage.value = errorMessageAsString(err, "Conversion failed.");
}
}
Expand Down Expand Up @@ -148,14 +165,14 @@ async function clickedDatatypeChange(selectedDatatype: any) {
});
if (error) {
errorMessage.value = errorMessageAsString(error, "History import failed.");
errorMessage.value = errorMessageAsString(error, "Datatype change failed.");
return;
}
successMessage.value = "Datatype changed successfully.";
}
function handleError(err: any) {
errorMessage.value = errorMessageAsString(err, "History import failed.");
errorMessage.value = errorMessageAsString(err, "Datatype conversion failed.");
if (err?.data?.stderr) {
jobError.value = err.data;
Expand Down Expand Up @@ -198,14 +215,14 @@ async function saveAttrs() {
{{ localize(infoMessage) }}
</BAlert>

<BAlert v-if="jobError" show variant="danger" dismissible>
<BAlert v-if="errorMessage" show variant="danger">
{{ localize(errorMessage) }}
</BAlert>

<BAlert v-if="successMessage" show variant="success" dismissible>
{{ localize(successMessage) }}
</BAlert>
<BTabs class="mt-3">
<BTabs v-if="!errorMessage" class="mt-3">
<BTab title-link-class="collection-edit-attributes-nav" @click="updateInfoMessage('')">
<template v-slot:title>
<FontAwesomeIcon :icon="faBars" class="mr-1" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import { canMutateHistory, isCollectionElement, isHDCA } from "@/api";
import ExpandedItems from "@/components/History/Content/ExpandedItems";
import { updateContentFields } from "@/components/History/model/queries";
import { useCollectionElementsStore } from "@/stores/collectionElementsStore";
import { errorMessageAsString } from "@/utils/simple-error";
import CollectionDetails from "./CollectionDetails.vue";
import CollectionNavigation from "./CollectionNavigation.vue";
import CollectionOperations from "./CollectionOperations.vue";
import Alert from "@/components/Alert.vue";
import ContentItem from "@/components/History/Content/ContentItem.vue";
import ListingLayout from "@/components/History/Layout/ListingLayout.vue";
Expand Down Expand Up @@ -54,6 +56,7 @@ watch(
const collectionElements = computed(() => collectionElementsStore.getCollectionElements(dsc.value) ?? []);
const loading = computed(() => collectionElementsStore.isLoadingCollectionElements(dsc.value));
const error = computed(() => collectionElementsStore.hasLoadingCollectionElementsError(dsc.value));
const jobState = computed(() => ("job_state_summary" in dsc.value ? dsc.value.job_state_summary : undefined));
const populatedStateMsg = computed(() =>
"populated_state_message" in dsc.value ? dsc.value.populated_state_message : undefined
Expand Down Expand Up @@ -120,7 +123,10 @@ watch(
</script>

<template>
<ExpandedItems v-slot="{ isExpanded, setExpanded }" :scope-key="dsc.id" :get-item-key="getItemKey">
<Alert v-if="error" variant="error">
{{ errorMessageAsString(error) }}
</Alert>
<ExpandedItems v-else v-slot="{ isExpanded, setExpanded }" :scope-key="dsc.id" :get-item-key="getItemKey">
<section class="dataset-collection-panel w-100 d-flex flex-column">
<section>
<CollectionNavigation
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<template>
<div>
<div v-for="(elVal, pvIndex) in parameter_value" :key="pvIndex">
<span v-if="elVal === null">No input provided</span>
<GenericHistoryItem
v-if="['hda', 'hdca', 'dce'].includes(elVal.src)"
v-else-if="['hda', 'hdca', 'dce'].includes(elVal.src)"
:item-id="elVal.id"
:item-src="elVal.src" />
<span v-else> {{ elVal.hid }}: {{ elVal.name }} </span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export default {
return `Invocation ${this.preferredOrEmptyString} Storage Location`;
},
intermediateModalTitle() {
return `Invocation {{ preferredOrEmptyString }} Storage Location (Intermediate Datasets)`;
return `Invocation ${this.preferredOrEmptyString} Storage Location (Intermediate Datasets)`;
},
suffixPrimary() {
if (this.splitObjectStore) {
Expand Down
16 changes: 15 additions & 1 deletion client/src/composables/keyedCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function useKeyedCache<T>(
) {
const storedItems = ref<{ [key: string]: T }>({});
const loadingItem = ref<{ [key: string]: boolean }>({});
const loadingErrors = ref<{ [key: string]: Error }>({});

const getItemById = computed(() => {
return (id: string) => {
Expand All @@ -67,10 +68,17 @@ export function useKeyedCache<T>(
};
});

const hasItemLoadError = computed(() => {
return (id: string) => {
return loadingErrors.value[id] ?? null;
};
});

async function fetchItemById(params: FetchParams) {
const itemId = params.id;
const isAlreadyLoading = loadingItem.value[itemId] ?? false;
if (isAlreadyLoading) {
const failedLoading = loadingErrors.value[itemId];
if (isAlreadyLoading || failedLoading) {
return;
}
set(loadingItem.value, itemId, true);
Expand All @@ -79,6 +87,8 @@ export function useKeyedCache<T>(
const item = await fetchItem({ id: itemId });
set(storedItems.value, itemId, item);
return item;
} catch (error) {
set(loadingErrors.value, itemId, error);
} finally {
del(loadingItem.value, itemId);
}
Expand All @@ -98,6 +108,10 @@ export function useKeyedCache<T>(
/**
* A computed function that returns true if the item with the given id is currently being fetched.
*/
hasItemLoadError,
/**
* A computed function holding errors
*/
isLoadingItem,
/**
* Fetches the item with the given id from the server.
Expand Down
4 changes: 3 additions & 1 deletion client/src/stores/collectionAttributesStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ export const useCollectionAttributesStore = defineStore("collectionAttributesSto
return data;
}

const { storedItems, getItemById, isLoadingItem } = useKeyedCache<DatasetCollectionAttributes>(fetchAttributes);
const { storedItems, getItemById, isLoadingItem, hasItemLoadError } =
useKeyedCache<DatasetCollectionAttributes>(fetchAttributes);

return {
storedAttributes: storedItems,
getAttributes: getItemById,
isLoadingAttributes: isLoadingItem,
hasItemLoadError: hasItemLoadError,
};
});
15 changes: 14 additions & 1 deletion client/src/stores/collectionElementsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const FETCH_LIMIT = 50;
export const useCollectionElementsStore = defineStore("collectionElementsStore", () => {
const storedCollections = ref<{ [key: string]: HDCASummary }>({});
const loadingCollectionElements = ref<{ [key: string]: boolean }>({});
const loadingCollectionElementsErrors = ref<{ [key: string]: Error }>({});
const storedCollectionElements = ref<{ [key: string]: DCEEntry[] }>({});

/**
Expand All @@ -62,6 +63,12 @@ export const useCollectionElementsStore = defineStore("collectionElementsStore",
};
});

const hasLoadingCollectionElementsError = computed(() => {
return (collection: CollectionEntry) => {
return loadingCollectionElementsErrors.value[getCollectionKey(collection) ?? false];
};
});

type FetchParams = {
storedElements: DCEEntry[];
collection: CollectionEntry;
Expand Down Expand Up @@ -105,6 +112,8 @@ export const useCollectionElementsStore = defineStore("collectionElementsStore",
});

return { fetchedElements, elementOffset: offset };
} catch (error) {
set(loadingCollectionElementsErrors.value, collectionKey, error);
} finally {
del(loadingCollectionElements.value, collectionKey);
}
Expand Down Expand Up @@ -161,7 +170,7 @@ export const useCollectionElementsStore = defineStore("collectionElementsStore",
/** Returns collection from storedCollections, will load collection if not in store */
const getCollectionById = computed(() => {
return (collectionId: string) => {
if (!storedCollections.value[collectionId]) {
if (!storedCollections.value[collectionId] && !loadingCollectionElementsErrors.value[collectionId]) {
// TODO: Try to remove this as it can cause computed side effects (use keyedCache in this store instead?)
fetchCollection({ id: collectionId });
}
Expand All @@ -175,6 +184,8 @@ export const useCollectionElementsStore = defineStore("collectionElementsStore",
const collection = await fetchCollectionDetails({ id: params.id });
set(storedCollections.value, collection.id, collection);
return collection;
} catch (error) {
set(loadingCollectionElementsErrors.value, params.id, error);
} finally {
del(loadingCollectionElements.value, params.id);
}
Expand Down Expand Up @@ -202,6 +213,8 @@ export const useCollectionElementsStore = defineStore("collectionElementsStore",
storedCollectionElements,
getCollectionElements,
isLoadingCollectionElements,
hasLoadingCollectionElementsError,
loadingCollectionElementsErrors,
getCollectionById,
fetchCollection,
invalidateCollectionElements,
Expand Down
13 changes: 8 additions & 5 deletions lib/galaxy/managers/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
datetime,
)
from typing import (
Any,
cast,
Dict,
List,
Optional,
Union,
)

import sqlalchemy
Expand Down Expand Up @@ -989,16 +991,17 @@ def inputs_recursive(input_params, param_values, depth=1, upgrade_messages=None)
or input.type == "data_collection"
or isinstance(input_value, model.HistoryDatasetAssociation)
):
value = []
value: List[Union[Dict[str, Any], None]] = []
for element in listify(input_value):
element_id = element.id
if isinstance(element, model.HistoryDatasetAssociation):
hda = element
value.append({"src": "hda", "id": element_id, "hid": hda.hid, "name": hda.name})
value.append({"src": "hda", "id": element.id, "hid": hda.hid, "name": hda.name})
elif isinstance(element, model.DatasetCollectionElement):
value.append({"src": "dce", "id": element_id, "name": element.element_identifier})
value.append({"src": "dce", "id": element.id, "name": element.element_identifier})
elif isinstance(element, model.HistoryDatasetCollectionAssociation):
value.append({"src": "hdca", "id": element_id, "hid": element.hid, "name": element.name})
value.append({"src": "hdca", "id": element.id, "hid": element.hid, "name": element.name})
elif element is None:
value.append(None)
else:
raise Exception(
f"Unhandled data input parameter type encountered {element.__class__.__name__}"
Expand Down
2 changes: 1 addition & 1 deletion lib/galaxy/schema/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ class JobParameter(Model):
title="Depth",
description="The depth of the job parameter.",
)
value: Optional[Union[List[EncodedJobParameterHistoryItem], float, int, bool, str]] = Field(
value: Optional[Union[List[Optional[EncodedJobParameterHistoryItem]], float, int, bool, str]] = Field(
default=None, title="Value", description="The values of the job parameter", union_mode="left_to_right"
)
notes: Optional[str] = Field(default=None, title="Notes", description="Notes associated with the job parameter.")
Expand Down
1 change: 1 addition & 0 deletions lib/galaxy/tools/actions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,7 @@ def handle_output(name, output, hidden=None):
output_collection.mark_as_populated()
for hdca in output_collections.out_collection_instances.values():
hdca.visible = False
hdca.collection.mark_as_populated()
object_store_populator = ObjectStorePopulator(trans.app, trans.user)
for data in out_data.values():
data.set_skipped(object_store_populator)
Expand Down
3 changes: 1 addition & 2 deletions lib/galaxy/tools/search/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@

def get_or_create_index(index_dir, schema):
"""Get or create a reference to the index."""
if not os.path.exists(index_dir):
os.makedirs(index_dir)
os.makedirs(index_dir, exist_ok=True)
if index.exists_in(index_dir):
idx = index.open_dir(index_dir)
if idx.schema == schema:
Expand Down
24 changes: 24 additions & 0 deletions lib/galaxy_test/api/test_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -2425,6 +2425,30 @@ def test_run_nested_conditional_workflow_steps(self):
if step["workflow_step_label"] == "cat1":
assert sum(1 for j in step["jobs"] if j["state"] == "skipped") == 1

def test_run_workflow_conditional_subworkflow_step_with_hdca_creation(self):
# Regression test, ensures scheduling proceeds even if a skipped step creates a collection
with self.dataset_populator.test_history() as history_id:
self._run_workflow(
"""
class: GalaxyWorkflow
inputs: []
steps:
conditional_subworkflow_step:
when: $(false)
run:
class: GalaxyWorkflow
inputs: []
steps:
create_collection:
tool_id: create_input_collection
flatten_collection:
tool_id: cat_list
in:
input1: create_collection/output
""",
history_id=history_id,
)

def test_run_workflow_conditional_step_map_over_expression_tool(self):
with self.dataset_populator.test_history() as history_id:
summary = self._run_workflow(
Expand Down
11 changes: 9 additions & 2 deletions lib/galaxy_test/base/populators.py
Original file line number Diff line number Diff line change
Expand Up @@ -1770,7 +1770,11 @@ def upload_yaml_workflow(self, yaml_content: YamlContentT, **kwds) -> str:
return workflow_id

def wait_for_invocation(
self, workflow_id: str, invocation_id: str, timeout: timeout_type = DEFAULT_TIMEOUT, assert_ok: bool = True
self,
workflow_id: Optional[str],
invocation_id: str,
timeout: timeout_type = DEFAULT_TIMEOUT,
assert_ok: bool = True,
) -> str:
url = f"invocations/{invocation_id}"

Expand Down Expand Up @@ -1818,7 +1822,7 @@ def invocation_count():

def wait_for_workflow(
self,
workflow_id: str,
workflow_id: Optional[str],
invocation_id: str,
history_id: str,
assert_ok: bool = True,
Expand All @@ -1827,6 +1831,9 @@ def wait_for_workflow(
"""Wait for a workflow invocation to completely schedule and then history
to be complete."""
self.wait_for_invocation(workflow_id, invocation_id, timeout=timeout, assert_ok=assert_ok)
for step in self.get_invocation(invocation_id)["steps"]:
if step["subworkflow_invocation_id"]:
self.wait_for_invocation(None, step["subworkflow_invocation_id"], timeout=timeout, assert_ok=assert_ok)
self.dataset_populator.wait_for_history_jobs(history_id, assert_ok=assert_ok, timeout=timeout)

def get_invocation(self, invocation_id, step_details=False):
Expand Down

0 comments on commit 9a231f7

Please sign in to comment.