From d71a7662ec3b91ec1ceec5851ddcef47b210bb1a Mon Sep 17 00:00:00 2001 From: Wolfgang Maier Date: Mon, 4 Mar 2024 17:17:49 +0100 Subject: [PATCH 0001/1145] Set minimal metadata also for empty bed datasets --- lib/galaxy/datatypes/interval.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/datatypes/interval.py b/lib/galaxy/datatypes/interval.py index 2fbac3ff7abe..233006a3bc65 100644 --- a/lib/galaxy/datatypes/interval.py +++ b/lib/galaxy/datatypes/interval.py @@ -509,8 +509,8 @@ class Bed(Interval): def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None: """Sets the metadata information for datasets previously determined to be in bed format.""" + i = 0 if dataset.has_data(): - i = 0 for i, line in enumerate(open(dataset.get_file_name())): # noqa: B007 line = line.rstrip("\r\n") if line and not line.startswith("#"): @@ -526,7 +526,7 @@ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> N if overwrite or not dataset.metadata.element_is_set("strandCol"): dataset.metadata.strandCol = 6 break - Tabular.set_meta(self, dataset, overwrite=overwrite, skip=i) + Tabular.set_meta(self, dataset, overwrite=overwrite, skip=i) def as_ucsc_display_file(self, dataset: DatasetProtocol, **kwd) -> Union[FileObjType, str]: """Returns file contents with only the bed data. If bed 6+, treat as interval.""" From a1bfbf5e58e372171c8a1c649c2b8f3455281a9b Mon Sep 17 00:00:00 2001 From: Matthias Bernt Date: Tue, 5 Mar 2024 09:49:11 +0100 Subject: [PATCH 0002/1145] auto bind galaxy_data_manager_data_path in containers --- lib/galaxy/app.py | 1 + lib/galaxy/tool_util/deps/container_classes.py | 3 +++ lib/galaxy/tool_util/deps/dependencies.py | 2 ++ 3 files changed, 6 insertions(+) diff --git a/lib/galaxy/app.py b/lib/galaxy/app.py index f45c9aa83abc..3eeca459473f 100644 --- a/lib/galaxy/app.py +++ b/lib/galaxy/app.py @@ -312,6 +312,7 @@ def _configure_toolbox(self): galaxy_root_dir=galaxy_root_dir, default_file_path=file_path, tool_data_path=self.config.tool_data_path, + galaxy_data_manager_data_path=self.config.galaxy_data_manager_data_path, shed_tool_data_path=self.config.shed_tool_data_path, outputs_to_working_directory=self.config.outputs_to_working_directory, container_image_cache_path=self.config.container_image_cache_path, diff --git a/lib/galaxy/tool_util/deps/container_classes.py b/lib/galaxy/tool_util/deps/container_classes.py index 6fade14c37b4..349ac73455e2 100644 --- a/lib/galaxy/tool_util/deps/container_classes.py +++ b/lib/galaxy/tool_util/deps/container_classes.py @@ -338,6 +338,7 @@ def add_var(name, value): add_var("default_file_path", self.app_info.default_file_path) add_var("library_import_dir", self.app_info.library_import_dir) add_var("tool_data_path", self.app_info.tool_data_path) + add_var("galaxy_data_manager_data_path", self.app_info.galaxy_data_manager_data_path) add_var("shed_tool_data_path", self.app_info.shed_tool_data_path) if self.job_info.job_directory and self.job_info.job_directory_type == "pulsar": @@ -376,6 +377,8 @@ def add_var(name, value): defaults += ",$library_import_dir:default_ro" if self.app_info.tool_data_path: defaults += ",$tool_data_path:default_ro" + if self.app_info.galaxy_data_manager_data_path: + defaults += ",$galaxy_data_manager_data_path:default_ro" if self.app_info.shed_tool_data_path: defaults += ",$shed_tool_data_path:default_ro" diff --git a/lib/galaxy/tool_util/deps/dependencies.py b/lib/galaxy/tool_util/deps/dependencies.py index cb5d571b44d5..4791c6d9506e 100644 --- a/lib/galaxy/tool_util/deps/dependencies.py +++ b/lib/galaxy/tool_util/deps/dependencies.py @@ -21,6 +21,7 @@ def __init__( galaxy_root_dir: Optional[str] = None, default_file_path: Optional[str] = None, tool_data_path: Optional[str] = None, + galaxy_data_manager_data_path: Optional[str] = None, shed_tool_data_path: Optional[str] = None, outputs_to_working_directory: bool = False, container_image_cache_path: Optional[str] = None, @@ -35,6 +36,7 @@ def __init__( self.galaxy_root_dir = galaxy_root_dir self.default_file_path = default_file_path self.tool_data_path = tool_data_path + self.galaxy_data_manager_data_path = galaxy_data_manager_data_path self.shed_tool_data_path = shed_tool_data_path # TODO: Vary default value for docker_volumes based on this... self.outputs_to_working_directory = outputs_to_working_directory From f2b299a97fa95fee33e696c7606c88b6ff87125b Mon Sep 17 00:00:00 2001 From: Wolfgang Maier Date: Tue, 5 Mar 2024 11:43:23 +0100 Subject: [PATCH 0003/1145] Improve documentation of metadata_source attribute --- lib/galaxy/tool_util/xsd/galaxy.xsd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/tool_util/xsd/galaxy.xsd b/lib/galaxy/tool_util/xsd/galaxy.xsd index e14bbd8a05fe..fb20832f0268 100644 --- a/lib/galaxy/tool_util/xsd/galaxy.xsd +++ b/lib/galaxy/tool_util/xsd/galaxy.xsd @@ -5402,8 +5402,8 @@ This only applies to collections that are mapped over a non-collection input and This copies the metadata information -from the tool's input dataset. This is particularly useful for interval data -types where the order of the columns is not set. +from the tool's input dataset to serve as default for information that cannot be detected from the output. +One prominent use case is interval data with a non-standard column order that cannot be deduced from a header line, but which is known to be identical in the input and output datasets. From 893ce11acc4b3fcf7e74a713f66e6ee23586a29d Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 2 Nov 2023 00:01:14 -0400 Subject: [PATCH 0004/1145] Add persistentToggle composable. This can be used for persisting the toggled state of particular sections in the UI. --- client/src/composables/persistentToggle.ts | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 client/src/composables/persistentToggle.ts diff --git a/client/src/composables/persistentToggle.ts b/client/src/composables/persistentToggle.ts new file mode 100644 index 000000000000..6b9b414aea1a --- /dev/null +++ b/client/src/composables/persistentToggle.ts @@ -0,0 +1,28 @@ +import { type Ref, ref, watch } from "vue"; + +interface ToggleStateInterface { + toggled: Ref; + toggle: () => void; +} + +export function usePersistentToggle(uniqueId: string): ToggleStateInterface{ + const localStorageKey = `toggle-state-${uniqueId}`; + + // Retrieve the toggled state from localStorage if available, otherwise default to false + const toggled = ref(localStorage.getItem(localStorageKey) === "true"); + + // Watch for changes in the toggled state and persist them to localStorage + watch(toggled, (newVal: boolean) => { + localStorage.setItem(localStorageKey, String(newVal)); + }); + + // Expose a function for toggling state + const toggle = () => { + toggled.value = !toggled.value; + }; + + return { + toggled, + toggle, + }; +} From 129d3c24a6b0a18dc781f05563b392b9b245a848 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Mon, 6 Nov 2023 14:01:17 -0500 Subject: [PATCH 0005/1145] Add basic collapse to heading -- work on styling... --- client/src/components/Common/Heading.vue | 9 +++++++++ client/src/composables/persistentToggle.ts | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/client/src/components/Common/Heading.vue b/client/src/components/Common/Heading.vue index 815680b6dc88..8454314c276c 100644 --- a/client/src/components/Common/Heading.vue +++ b/client/src/components/Common/Heading.vue @@ -14,10 +14,14 @@ interface Props { inline?: boolean; size?: "xl" | "lg" | "md" | "sm" | "text"; icon?: string | [string, string]; + collapsible?: boolean; + collapsed?: boolean; } const props = defineProps(); +defineEmits(["click"]); + const sizeClass = computed(() => { return `h-${props.size ?? "lg"}`; }); @@ -37,6 +41,10 @@ const element = computed(() => {
+
@@ -47,6 +55,7 @@ const element = computed(() => { :class="[sizeClass, props.bold ? 'font-weight-bold' : '', props.inline ? 'inline' : '']"> + diff --git a/client/src/composables/persistentToggle.ts b/client/src/composables/persistentToggle.ts index 6b9b414aea1a..154f4c2b4a42 100644 --- a/client/src/composables/persistentToggle.ts +++ b/client/src/composables/persistentToggle.ts @@ -5,7 +5,7 @@ interface ToggleStateInterface { toggle: () => void; } -export function usePersistentToggle(uniqueId: string): ToggleStateInterface{ +export function usePersistentToggle(uniqueId: string): ToggleStateInterface { const localStorageKey = `toggle-state-${uniqueId}`; // Retrieve the toggled state from localStorage if available, otherwise default to false From 75cbcab6df2f6d645b6064c9de4c21a2759f6e6f Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Mon, 6 Nov 2023 14:08:10 -0500 Subject: [PATCH 0006/1145] Swap AWSEstimate to use persistentToggle --- .../src/components/JobMetrics/AwsEstimate.vue | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/client/src/components/JobMetrics/AwsEstimate.vue b/client/src/components/JobMetrics/AwsEstimate.vue index da194e56a6d4..ae8e4a686609 100644 --- a/client/src/components/JobMetrics/AwsEstimate.vue +++ b/client/src/components/JobMetrics/AwsEstimate.vue @@ -1,6 +1,10 @@ diff --git a/client/src/components/Common/index.ts b/client/src/components/Common/index.ts new file mode 100644 index 000000000000..3c9557801eac --- /dev/null +++ b/client/src/components/Common/index.ts @@ -0,0 +1,2 @@ +// TODO: Not sure if this is the best place for this type +export type ColorVariant = "primary" | "secondary" | "success" | "danger" | "warning" | "info" | "light" | "dark"; diff --git a/client/src/components/Common/models/exportRecordModel.ts b/client/src/components/Common/models/exportRecordModel.ts index 10a8da3d3bab..cb218818f879 100644 --- a/client/src/components/Common/models/exportRecordModel.ts +++ b/client/src/components/Common/models/exportRecordModel.ts @@ -29,7 +29,7 @@ export interface ExportRecord { readonly stsDownloadId?: string; readonly isStsDownload: boolean; readonly canDownload: boolean; - readonly modelStoreFormat: string; + readonly modelStoreFormat: ModelStoreFormat; readonly exportParams?: ExportParams; readonly duration?: number | null; readonly canExpire: boolean; @@ -62,17 +62,24 @@ export class ExportParamsModel implements ExportParams { return Boolean(this._params?.include_hidden); } - public equals(otherExportParams?: ExportParamsModel) { + public equals(otherExportParams?: ExportParams) { if (!otherExportParams) { return false; } - return ( - this.modelStoreFormat === otherExportParams.modelStoreFormat && - this.includeFiles === otherExportParams.includeFiles && - this.includeDeleted === otherExportParams.includeDeleted && - this.includeHidden === otherExportParams.includeHidden - ); + return sameExportParams(this, otherExportParams); + } +} + +export function sameExportParams(params1?: ExportParams, params2?: ExportParams): boolean { + if (!params1 || !params2) { + return false; } + return ( + params1.modelStoreFormat === params2.modelStoreFormat && + params1.includeFiles === params2.includeFiles && + params1.includeDeleted === params2.includeDeleted && + params1.includeHidden === params2.includeHidden + ); } export class ExportRecordModel implements ExportRecord { diff --git a/client/src/components/History/Export/ExportOptions.vue b/client/src/components/History/Export/ExportOptions.vue index 7a12c293de72..8b03d8533bc5 100644 --- a/client/src/components/History/Export/ExportOptions.vue +++ b/client/src/components/History/Export/ExportOptions.vue @@ -3,14 +3,13 @@ import { BCard, BCollapse, BFormCheckbox, BFormGroup, BFormSelect, BLink } from import { computed, reactive, ref } from "vue"; import { AVAILABLE_EXPORT_FORMATS } from "@/api/histories.export"; -import { ExportParamsModel } from "@/components/Common/models/exportRecordModel"; +import type { ExportParams } from "@/components/Common/models/exportRecordModel"; -const props = defineProps({ - exportParams: { - type: ExportParamsModel, - required: true, - }, -}); +interface Props { + exportParams: ExportParams; +} + +const props = defineProps(); const emit = defineEmits(["onValueChanged"]); diff --git a/client/src/components/Workflow/WorkflowActions.vue b/client/src/components/Workflow/WorkflowActions.vue index 52d6037ff89b..be67fccc429a 100644 --- a/client/src/components/Workflow/WorkflowActions.vue +++ b/client/src/components/Workflow/WorkflowActions.vue @@ -20,6 +20,8 @@ import { useConfirmDialog } from "@/composables/confirmDialog"; import { Toast } from "@/composables/toast"; import { useUserStore } from "@/stores/userStore"; +import type { ColorVariant } from "../Common"; + import AsyncButton from "@/components/Common/AsyncButton.vue"; library.add(faCaretDown, faExternalLinkAlt, faEye, faFileExport, farStar, faStar, faTrash); @@ -41,7 +43,7 @@ type BaseAction = { target?: "_blank"; size: "sm" | "md" | "lg"; component: "async" | "button"; - variant: "primary" | "secondary" | "success" | "danger" | "warning" | "info" | "light" | "dark" | "link"; + variant: ColorVariant | "link"; onClick?: (e?: MouseEvent | KeyboardEvent) => void; }; From df3becf9ad0925119ff6b283203fd07daf35b7ab Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Thu, 18 Apr 2024 15:08:31 +0200 Subject: [PATCH 0086/1145] Refactor HistoryExport and convert to Typescript --- .../History/Export/HistoryExport.vue | 77 ++++++++++--------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/client/src/components/History/Export/HistoryExport.vue b/client/src/components/History/Export/HistoryExport.vue index ae1b55dae7d2..fb6d7a9d47bd 100644 --- a/client/src/components/History/Export/HistoryExport.vue +++ b/client/src/components/History/Export/HistoryExport.vue @@ -1,15 +1,9 @@ - diff --git a/client/src/components/Common/FilterMenuInput.vue b/client/src/components/Common/FilterMenuInput.vue index 7cf541a4c314..011e12a73ae0 100644 --- a/client/src/components/Common/FilterMenuInput.vue +++ b/client/src/components/Common/FilterMenuInput.vue @@ -14,7 +14,7 @@ import { import { capitalize } from "lodash"; import { computed, ref, watch } from "vue"; -import { type ErrorType, type ValidFilter } from "@/utils/filtering"; +import { type ValidFilter } from "@/utils/filtering"; library.add(faQuestion); @@ -23,7 +23,7 @@ type FilterType = string | boolean | undefined; interface Props { name: string; identifier: any; - error?: ErrorType; + error?: string; filter: ValidFilter; filters: { [k: string]: FilterType; @@ -45,13 +45,6 @@ const localValue = ref(propValue.value); const helpToggle = ref(false); const modalTitle = `${capitalize(props.filter.placeholder)} Help`; -function hasError(field: string) { - if (props.error && props.error.index == field) { - return props.error.typeError || props.error.msg; - } - return ""; -} - function onHelp(_: string, value: string) { helpToggle.value = false; localValue.value = value; @@ -81,10 +74,10 @@ watch( :id="`${identifier}-advanced-filter-${props.name}`" ref="filterMenuInput" v-model="localValue" - v-b-tooltip.focus.v-danger="hasError(props.name)" + v-b-tooltip.focus.v-danger="props.error" class="mw-100" size="sm" - :state="hasError(props.name) ? false : null" + :state="props.error ? false : null" :placeholder="`any ${props.filter.placeholder}`" :list="props.filter.datalist ? `${identifier}-${props.name}-selectList` : null" @keyup.enter="emit('on-enter')" diff --git a/client/src/components/History/HistoryFilters.js b/client/src/components/History/HistoryFilters.js index 2b9788d8145c..2442e564e056 100644 --- a/client/src/components/History/HistoryFilters.js +++ b/client/src/components/History/HistoryFilters.js @@ -17,6 +17,16 @@ const validFilters = { name: { placeholder: "name", type: String, handler: contains("name"), menuItem: true }, name_eq: { handler: equals("name"), menuItem: false }, extension: { placeholder: "extension", type: String, handler: equals("extension"), menuItem: true }, + history_content_type: { + placeholder: "content type", + type: "Dropdown", + handler: equals("history_content_type"), + datalist: [ + { value: "dataset", text: "Datasets Only" }, + { value: "dataset_collection", text: "Collections Only" }, + ], + menuItem: true, + }, tag: { placeholder: "tag", type: String, handler: contains("tags", "tag", expandNameTag), menuItem: true }, state: { placeholder: "state", diff --git a/client/src/utils/filtering.ts b/client/src/utils/filtering.ts index 9f6ceeee6ddc..26b144eee659 100644 --- a/client/src/utils/filtering.ts +++ b/client/src/utils/filtering.ts @@ -40,6 +40,15 @@ export type ErrorType = { type OperatorForAlias = typeof operatorForAlias; export type Alias = keyof OperatorForAlias; type Operator = OperatorForAlias[Alias]; +type FilterType = + | typeof String + | typeof Number + | typeof Boolean + | typeof Date + | "MultiTags" + | "ObjectStore" + | "QuotaSource" + | "Dropdown"; /** A ValidFilter with a `handler` for the `Filtering` class, * and remaining properties for the `FilterMenu` component @@ -48,7 +57,7 @@ export type ValidFilter = { /** The `FilterMenu` input field/tooltip/label placeholder */ placeholder?: string; /** The data type of the `FilterMenu` input field */ - type?: typeof String | typeof Number | typeof Boolean | typeof Date | "MultiTags" | "ObjectStore" | "QuotaSource"; + type?: FilterType; /** If type: Boolean: * - booleanType: 'default' creates: `filter:true|false|any` * - booleanType: 'is' creates: `is:filter` @@ -60,7 +69,7 @@ export type ValidFilter = { * (if `false` the filter is still valid for the search bar `filterText`) */ menuItem: boolean; - /** Is there a datalist of values for this field */ + /** The datalist of values for this field */ datalist?: | string[] | { From 11fffcfa66b4156b816564ad0aa69707241198f7 Mon Sep 17 00:00:00 2001 From: Ahmed Awan Date: Mon, 22 Apr 2024 12:39:29 -0500 Subject: [PATCH 0122/1145] advanced tool search: change `ontologyList` to a computed ref ... so that it is populated as soon as you change the panel view. --- client/src/components/Panels/Common/ToolSearch.vue | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/components/Panels/Common/ToolSearch.vue b/client/src/components/Panels/Common/ToolSearch.vue index 31592c803dc9..3d4a0629f0b2 100644 --- a/client/src/components/Panels/Common/ToolSearch.vue +++ b/client/src/components/Panels/Common/ToolSearch.vue @@ -100,7 +100,7 @@ const validFilters: ComputedRef>> = computed( placeholder: "EDAM ontology", type: String, handler: contains("ontology"), - datalist: ontologyList, + datalist: ontologyList.value, menuItem: true, }, id: { placeholder: "id", type: String, handler: contains("id"), menuItem: true }, @@ -115,9 +115,9 @@ const toolStore = useToolStore(); const { searchWorker } = storeToRefs(toolStore); const sectionNames = toolStore.sectionDatalist("default").map((option: { value: string; text: string }) => option.text); -const ontologyList = toolStore - .sectionDatalist("ontology:edam_topics") - .concat(toolStore.sectionDatalist("ontology:edam_operations")); +const ontologyList = computed(() => + toolStore.sectionDatalist("ontology:edam_topics").concat(toolStore.sectionDatalist("ontology:edam_operations")) +); onMounted(() => { // initialize worker From 857a14e2924c1b60f7968b082f44ef05a5867244 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 22 Apr 2024 19:29:43 +0200 Subject: [PATCH 0123/1145] Change wrong quota_source value from KeyError to ValueError Which is caught in https://github.com/galaxyproject/galaxy/blob/44782a44c14885df2d9d1bcb91698245bfb3aed8/lib/galaxy/managers/base.py#L1121-L1124 Fixes https://sentry.galaxyproject.org/share/issue/16a3342631224d8292ef792298175dea/: ``` KeyError: "Could not find key nul in object store keys [None, 'scratch']" File "starlette/applications.py", line 123, in __call__ await self.middleware_stack(scope, receive, send) File "starlette/middleware/errors.py", line 186, in __call__ raise exc File "starlette/middleware/errors.py", line 164, in __call__ await self.app(scope, receive, _send) File "starlette_context/middleware/raw_middleware.py", line 92, in __call__ await self.app(scope, receive, send_wrapper) File "starlette/middleware/base.py", line 189, in __call__ with collapse_excgroups(): File "contextlib.py", line 155, in __exit__ self.gen.throw(typ, value, traceback) File "starlette/_utils.py", line 93, in collapse_excgroups raise exc File "starlette/middleware/base.py", line 191, in __call__ response = await self.dispatch_func(request, call_next) File "galaxy/webapps/galaxy/fast_app.py", line 108, in add_x_frame_options response = await call_next(request) File "starlette/middleware/base.py", line 165, in call_next raise app_exc File "starlette/middleware/base.py", line 151, in coro await self.app(scope, receive_or_disconnect, send_no_error) File "starlette/middleware/exceptions.py", line 62, in __call__ await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) File "starlette/_exception_handler.py", line 64, in wrapped_app raise exc File "starlette/_exception_handler.py", line 53, in wrapped_app await app(scope, receive, sender) File "starlette/routing.py", line 758, in __call__ await self.middleware_stack(scope, receive, send) File "starlette/routing.py", line 778, in app await route.handle(scope, receive, send) File "starlette/routing.py", line 299, in handle await self.app(scope, receive, send) File "starlette/routing.py", line 79, in app await wrap_app_handling_exceptions(app, request)(scope, receive, send) File "starlette/_exception_handler.py", line 64, in wrapped_app raise exc File "starlette/_exception_handler.py", line 53, in wrapped_app await app(scope, receive, sender) File "starlette/routing.py", line 74, in app response = await func(request) File "fastapi/routing.py", line 278, in app raw_response = await run_endpoint_function( File "fastapi/routing.py", line 193, in run_endpoint_function return await run_in_threadpool(dependant.call, **values) File "starlette/concurrency.py", line 42, in run_in_threadpool return await anyio.to_thread.run_sync(func, *args) File "anyio/to_thread.py", line 56, in run_sync return await get_async_backend().run_sync_in_worker_thread( File "anyio/_backends/_asyncio.py", line 2144, in run_sync_in_worker_thread return await future File "anyio/_backends/_asyncio.py", line 851, in run result = context.run(func, *args) File "galaxy/webapps/galaxy/api/history_contents.py", line 465, in index items = self.service.index( File "galaxy/webapps/galaxy/services/history_contents.py", line 312, in index return self.__index_v2(trans, history_id, params, serialization_params, filter_query_params, accept) File "galaxy/webapps/galaxy/services/history_contents.py", line 972, in __index_v2 filters = self.history_contents_filters.parse_query_filters_with_relations(filter_query_params, history_id) File "galaxy/managers/history_contents.py", line 525, in parse_query_filters_with_relations return super().parse_query_filters(query_filters) File "galaxy/managers/base.py", line 1087, in parse_query_filters return self.parse_filters(filter_params) File "galaxy/managers/base.py", line 1096, in parse_filters filter_ = self.parse_filter(attr, op, val) File "galaxy/managers/base.py", line 1116, in parse_filter orm_filter = self._parse_orm_filter(attr, op, val) File "galaxy/managers/history_contents.py", line 601, in _parse_orm_filter if (column_filter := get_filter(attr, op, val)) is not None: File "galaxy/managers/history_contents.py", line 595, in get_filter raise KeyError(f"Could not find key {val} in object store keys {list(ids.keys())}") ``` --- lib/galaxy/managers/history_contents.py | 2 +- test/integration/test_quota.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/managers/history_contents.py b/lib/galaxy/managers/history_contents.py index a07953918738..adf680ade8c3 100644 --- a/lib/galaxy/managers/history_contents.py +++ b/lib/galaxy/managers/history_contents.py @@ -592,7 +592,7 @@ def get_filter(attr, op, val): if val == "__null__": val = None if val not in ids: - raise KeyError(f"Could not find key {val} in object store keys {list(ids.keys())}") + raise ValueError(f"Could not find key {val} in object store keys {list(ids.keys())}") object_store_ids = ids[val] return sql.column("object_store_id").in_(object_store_ids) diff --git a/test/integration/test_quota.py b/test/integration/test_quota.py index 24928ef0e007..07137786a353 100644 --- a/test/integration/test_quota.py +++ b/test/integration/test_quota.py @@ -196,6 +196,13 @@ def test_quota_source_label_basics(self): labels = [q["quota_source_label"] for q in quotas] assert "mylabel" in labels + with self.dataset_populator.test_history() as history_id: + response = self.dataset_populator._get_contents_request( + history_id, data={"q": "quota_source_label-eq", "qv": "invalid", "v": "dev"} + ) + assert response.status_code == 400 + assert "unparsable value for filter" in response.json()["err_msg"] + def _create_quota_with_name(self, quota_name: str, is_default: bool = False): payload = self._build_quota_payload_with_name(quota_name, is_default) create_response = self._post("quotas", data=payload, json=True) From 774939b5a87bae777ee1f756d45d584c1c930693 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Fri, 19 Apr 2024 12:05:44 -0400 Subject: [PATCH 0124/1145] More typing in files. --- lib/galaxy/files/__init__.py | 15 +++++++++------ lib/galaxy/files/sources/__init__.py | 2 ++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/galaxy/files/__init__.py b/lib/galaxy/files/__init__.py index 39b47887334a..3ddf2a35b1da 100644 --- a/lib/galaxy/files/__init__.py +++ b/lib/galaxy/files/__init__.py @@ -13,6 +13,7 @@ from galaxy import exceptions from galaxy.files.sources import ( BaseFilesSource, + FilesSourceProperties, PluginKind, ) from galaxy.util import plugin_config @@ -39,6 +40,8 @@ class NoMatchingFileSource(Exception): class ConfiguredFileSources: """Load plugins and resolve Galaxy URIs to FileSource objects.""" + _file_sources: List[BaseFilesSource] + def __init__( self, file_sources_config: "ConfiguredFileSourcesConfig", @@ -48,7 +51,7 @@ def __init__( ): self._file_sources_config = file_sources_config self._plugin_classes = self._file_source_plugins_dict() - file_sources = [] + file_sources: List[BaseFilesSource] = [] if conf_file is not None: file_sources = self._load_plugins_from_file(conf_file) elif conf_dict is not None: @@ -79,7 +82,7 @@ def _ensure_loaded(plugin_type): if stock_file_source_conf_dict: stock_plugin_source = plugin_config.plugin_source_from_dict(stock_file_source_conf_dict) - # insert at begining instead of append so FTP and library import appear + # insert at beginning instead of append so FTP and library import appear # at the top of the list (presumably the most common options). Admins can insert # these explicitly for greater control. file_sources = self._parse_plugin_source(stock_plugin_source) + file_sources @@ -107,7 +110,7 @@ def _parse_plugin_source(self, plugin_source): dict_to_list_key="id", ) - def find_best_match(self, url: str): + def find_best_match(self, url: str) -> Optional[BaseFilesSource]: """Returns the best matching file source for handling a particular url. Each filesource scores its own ability to match a particular url, and the highest scorer with a score > 0 is selected.""" scores = [FileSourceScore(file_source, file_source.score_url_match(url)) for file_source in self._file_sources] @@ -124,7 +127,7 @@ def get_file_source_path(self, uri): path = file_source.to_relative_path(uri) return FileSourcePath(file_source, path) - def validate_uri_root(self, uri, user_context): + def validate_uri_root(self, uri: str, user_context: "ProvidesUserFileSourcesUserContext"): # validate a URI against Galaxy's configuration, environment, and the current # user. Throw appropriate exception if there is a problem with the files source # referenced by the URI. @@ -171,8 +174,8 @@ def plugins_to_dict( browsable_only: Optional[bool] = False, include_kind: Optional[Set[PluginKind]] = None, exclude_kind: Optional[Set[PluginKind]] = None, - ) -> List[Dict[str, Any]]: - rval = [] + ) -> List[FilesSourceProperties]: + rval: List[FilesSourceProperties] = [] for file_source in self._file_sources: if not file_source.user_has_access(user_context): continue diff --git a/lib/galaxy/files/sources/__init__.py b/lib/galaxy/files/sources/__init__.py index 61d6a328108e..553fe385a6f4 100644 --- a/lib/galaxy/files/sources/__init__.py +++ b/lib/galaxy/files/sources/__init__.py @@ -267,6 +267,8 @@ class FilesSource(SingleFileSource, SupportsBrowsing): implements the `SupportsBrowsing` interface. """ + plugin_type: ClassVar[str] + @abc.abstractmethod def get_browsable(self) -> bool: """Return true if the filesource implements the SupportsBrowsing interface.""" From 0f993f62a6283439c12180a4e2143746b4bfb9fe Mon Sep 17 00:00:00 2001 From: John Chilton Date: Mon, 22 Apr 2024 13:57:36 -0400 Subject: [PATCH 0125/1145] Is this right? --- lib/galaxy/files/sources/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/galaxy/files/sources/__init__.py b/lib/galaxy/files/sources/__init__.py index 553fe385a6f4..80745cd4d41a 100644 --- a/lib/galaxy/files/sources/__init__.py +++ b/lib/galaxy/files/sources/__init__.py @@ -275,7 +275,6 @@ def get_browsable(self) -> bool: class BaseFilesSource(FilesSource): - plugin_type: ClassVar[str] plugin_kind: ClassVar[PluginKind] = PluginKind.rfs # Remote File Source by default, override in subclasses def get_browsable(self) -> bool: From 0a8243faf68856b7565873dba2256d0e29b8a0a7 Mon Sep 17 00:00:00 2001 From: Ahmed Awan Date: Mon, 22 Apr 2024 13:16:59 -0500 Subject: [PATCH 0126/1145] convert `state` filter from `String` to `Dropdown` Fixes https://github.com/galaxyproject/galaxy/issues/16366 --- client/src/components/History/HistoryFilters.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/History/HistoryFilters.js b/client/src/components/History/HistoryFilters.js index 2442e564e056..1bfcaad32cc8 100644 --- a/client/src/components/History/HistoryFilters.js +++ b/client/src/components/History/HistoryFilters.js @@ -30,7 +30,7 @@ const validFilters = { tag: { placeholder: "tag", type: String, handler: contains("tags", "tag", expandNameTag), menuItem: true }, state: { placeholder: "state", - type: String, + type: "Dropdown", handler: equals("state"), datalist: states, helpInfo: StatesInfo, From 5fa26177275e6e74e77b69c0f18dcc06d2ece207 Mon Sep 17 00:00:00 2001 From: Martin Cech Date: Mon, 22 Apr 2024 12:49:01 -0700 Subject: [PATCH 0127/1145] readd a bco.json invocation export API test --- lib/galaxy_test/api/test_workflows.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/galaxy_test/api/test_workflows.py b/lib/galaxy_test/api/test_workflows.py index 4eec65904541..89129d1dcb68 100644 --- a/lib/galaxy_test/api/test_workflows.py +++ b/lib/galaxy_test/api/test_workflows.py @@ -3082,6 +3082,15 @@ def test_workflow_invocation_report_custom(self): assert "## Workflow Inputs" in markdown_content assert "## About This Report" in markdown_content + @skip_without_tool("cat1") + def test_export_invocation_bco(self): + with self.dataset_populator.test_history() as history_id: + summary = self._run_workflow(WORKFLOW_SIMPLE, test_data={"input1": "hello world"}, history_id=history_id) + invocation_id = summary.invocation_id + bco = self.workflow_populator.download_invocation_to_store(invocation_id, extension="bco.json") + self.workflow_populator.validate_biocompute_object(bco) + assert bco["provenance_domain"]["name"] == "Simple Workflow" + @skip_without_tool("cat1") def test_export_invocation_ro_crate(self): with self.dataset_populator.test_history() as history_id: From 5745a5c0dc61d55e05cded64ed0e935f9bb01070 Mon Sep 17 00:00:00 2001 From: Ahmed Awan Date: Mon, 22 Apr 2024 14:51:24 -0500 Subject: [PATCH 0128/1145] remove `FilterMenuQuotaSource` and use `FilterMenuDropdown` instead --- client/src/components/Common/FilterMenu.vue | 13 +-- .../components/Common/FilterMenuDropdown.vue | 94 ++++++++++++---- .../Common/FilterMenuQuotaSource.vue | 101 ------------------ client/src/utils/filtering.ts | 2 +- 4 files changed, 80 insertions(+), 130 deletions(-) delete mode 100644 client/src/components/Common/FilterMenuQuotaSource.vue diff --git a/client/src/components/Common/FilterMenu.vue b/client/src/components/Common/FilterMenu.vue index 9fc5982054a9..0f0596e2f422 100644 --- a/client/src/components/Common/FilterMenu.vue +++ b/client/src/components/Common/FilterMenu.vue @@ -15,7 +15,6 @@ import FilterMenuDropdown from "@/components/Common/FilterMenuDropdown.vue"; import FilterMenuInput from "@/components/Common/FilterMenuInput.vue"; import FilterMenuMultiTags from "@/components/Common/FilterMenuMultiTags.vue"; import FilterMenuObjectStore from "@/components/Common/FilterMenuObjectStore.vue"; -import FilterMenuQuotaSource from "@/components/Common/FilterMenuQuotaSource.vue"; import FilterMenuRanged from "@/components/Common/FilterMenuRanged.vue"; library.add(faAngleDoubleUp, faQuestion, faSearch); @@ -260,15 +259,11 @@ function updateFilterText(newFilterText: string) { :filter="getValidFilter(filter)" :filters="filters" @change="onOption" /> - ; + +type FilterValue = QuotaUsageUnwrapped | string | boolean | undefined; + type DatalistItem = { value: string; text: string }; interface Props { + type?: FilterType; name: string; error?: string; filter: ValidFilter; filters: { - [k: string]: FilterType; + [k: string]: FilterValue; }; identifier: string; } @@ -26,12 +36,25 @@ interface Props { const props = defineProps(); const emit = defineEmits<{ - (e: "change", name: string, value: FilterType): void; + (e: "change", name: string, value: FilterValue): void; }>(); -const propValue = computed(() => props.filters[props.name]); +const propValue = computed(() => props.filters[props.name]); -const localValue = ref(propValue.value); +const localValue = ref(propValue.value); + +watch( + () => localValue.value, + () => { + emit("change", props.name, localValue.value); + } +); +watch( + () => propValue.value, + () => { + localValue.value = propValue.value; + } +); // datalist refs const datalist = computed<(DatalistItem[] | string[]) | undefined>(() => props.filter.datalist); @@ -56,20 +79,39 @@ function onHelp(_: string, value: string) { localValue.value = value; } -watch( - () => localValue.value, - () => { - emit("change", props.name, localValue.value); +// Quota Source refs and operations +const quotaUsages = ref([] as QuotaUsage[]); +const errorMessage = ref(); +async function loadQuotaUsages() { + try { + quotaUsages.value = await fetch(); + + // if the propValue is a string, find the corresponding QuotaUsage object and update the localValue + if (propValue.value && typeof propValue.value === "string") { + localValue.value = quotaUsages.value.find( + (quotaUsage) => props.filter.handler.converter!(quotaUsage) === propValue.value + ); + } + } catch (e) { + errorMessage.value = errorMessageAsString(e); } -); -watch( - () => propValue.value, - () => { - localValue.value = propValue.value; +} +const hasMultipleQuotaSources = computed(() => { + return !!(quotaUsages.value && quotaUsages.value.length > 1); +}); +onMounted(async () => { + if (props.type === "QuotaSource") { + await loadQuotaUsages(); } -); +}); +function isQuotaUsageVal(value: FilterValue): value is QuotaUsageUnwrapped { + return !!(value && value instanceof Object && "rawSourceLabel" in value); +} const dropDownText = computed(() => { + if (props.type === "QuotaSource" && isQuotaUsageVal(localValue.value)) { + return localValue.value.sourceLabel; + } if (localValue.value) { const stringMatch = stringDatalist.value.find((item) => item === localValue.value); const objectMatch = objectDatalist.value.find((item) => item.value === localValue.value); @@ -82,13 +124,13 @@ const dropDownText = computed(() => { return "(any)"; }); -function setValue(val: string | undefined) { +function setValue(val: string | QuotaUsage | undefined) { localValue.value = val; }
-
+
From 97efceafce4c223aaf81ee33ab05023ad9632f7a Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Fri, 26 Apr 2024 11:25:55 -0400 Subject: [PATCH 0161/1145] Explicitly specify that outputFormat is a string -- this fixes tsc getting confused below when reassigning --- client/src/components/Citation/CitationsList.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/Citation/CitationsList.vue b/client/src/components/Citation/CitationsList.vue index 00b32419db75..7aa5b137ff97 100644 --- a/client/src/components/Citation/CitationsList.vue +++ b/client/src/components/Citation/CitationsList.vue @@ -29,7 +29,7 @@ const { config } = useConfig(true); const emit = defineEmits(["rendered", "show", "shown", "hide", "hidden"]); -const outputFormat = ref(outputFormats.CITATION); +const outputFormat = ref(outputFormats.CITATION); const citations = ref([]); onUpdated(() => { From 6b6bcf5585fdbcb069805523ddb6c30c9f9c6a30 Mon Sep 17 00:00:00 2001 From: Ahmed Awan Date: Thu, 11 Apr 2024 13:02:36 -0500 Subject: [PATCH 0162/1145] Add an invocation graph view to the invocation summary This graph view utilizes the same workflow editor canvas, and instead of showing step details on each step node, it shows the job states for the steps. Clicking on a step expands the details for the invocation step. The `useInvocationGraph` composable loads the graph based on the original workflow id and invocation object; and the step info is loaded via the `step_jobs_summary` api route. --- .../src/components/Workflow/Editor/Node.vue | 59 +++- .../components/Workflow/Editor/NodeInput.vue | 38 ++- .../Workflow/Editor/NodeInvocationText.vue | 39 +++ .../components/Workflow/Editor/NodeOutput.vue | 3 +- .../Workflow/Editor/WorkflowGraph.vue | 16 +- .../Invocation/Graph/InvocationGraph.vue | 300 ++++++++++++++++++ .../components/Workflow/InvocationsList.vue | 12 +- .../Workflow/{constants.js => constants.ts} | 2 +- .../WorkflowInvocationState/JobStep.vue | 62 +++- .../WorkflowInvocationState.vue | 5 + .../WorkflowInvocationStep.vue | 68 +++- .../WorkflowInvocationSummary.vue | 142 +++++---- client/src/composables/useInvocationGraph.ts | 271 ++++++++++++++++ client/src/style/scss/base.scss | 12 + 14 files changed, 937 insertions(+), 92 deletions(-) create mode 100644 client/src/components/Workflow/Editor/NodeInvocationText.vue create mode 100644 client/src/components/Workflow/Invocation/Graph/InvocationGraph.vue rename client/src/components/Workflow/{constants.js => constants.ts} (68%) create mode 100644 client/src/composables/useInvocationGraph.ts diff --git a/client/src/components/Workflow/Editor/Node.vue b/client/src/components/Workflow/Editor/Node.vue index 3b29ed433f27..144191a60d7e 100644 --- a/client/src/components/Workflow/Editor/Node.vue +++ b/client/src/components/Workflow/Editor/Node.vue @@ -12,8 +12,10 @@ :disabled="readonly" @move="onMoveTo" @pan-by="onPanBy"> +
@@ -74,6 +76,12 @@ >{{ step.id + 1 }}: {{ title }} + + +
{{ errors }} -
+ +
-
+
+ , default: null }, name: { type: String as PropType, default: null }, - step: { type: Object as PropType, required: true }, + step: { type: Object as PropType, required: true }, datatypesMapper: { type: DatatypesMapperModel, required: true }, activeNodeId: { type: null as unknown as PropType, @@ -164,6 +185,7 @@ const props = defineProps({ scroll: { type: Object as PropType, required: true }, scale: { type: Number, default: 1 }, highlight: { type: Boolean, default: false }, + isInvocation: { type: Boolean, default: false }, readonly: { type: Boolean, default: false }, }); @@ -219,6 +241,23 @@ const style = computed(() => { return { top: props.step.position!.top + "px", left: props.step.position!.left + "px" }; }); const errors = computed(() => props.step.errors || stateStore.getStepLoadingState(props.id)?.error); +const headerClass = computed(() => { + let cls; + if (props.isInvocation) { + cls = "cursor-pointer"; + if (invocationStep.value.headerClass) { + cls += ` ${invocationStep.value.headerClass}`; + } else { + cls += " node-header"; + } + } else { + cls = "node-header"; + if (!props.readonly && !props.isInvocation) { + cls += " cursor-move"; + } + } + return cls; +}); const inputs = computed(() => { const connections = connectionStore.getConnectionsForStep(props.id); const extraStepInputs = stepStore.getStepExtraInputs(props.id); @@ -247,6 +286,7 @@ const invalidOutputs = computed(() => { return { name, optional: false, datatypes: [], valid: false }; }); }); +const invocationStep = computed(() => props.step as GraphStep); const outputs = computed(() => { return [...props.step.outputs, ...invalidOutputs.value]; }); @@ -317,12 +357,21 @@ function makeActive() { } .node-header { - cursor: move; background: $brand-primary; color: $white; + &.cursor-move { + cursor: move; + } } .node-body { + .invocation-node-output { + position: absolute; + right: 0; + top: 0; + width: 100%; + height: 100%; + } .rule { height: 0; border: none; diff --git a/client/src/components/Workflow/Editor/NodeInput.vue b/client/src/components/Workflow/Editor/NodeInput.vue index b4ab0ac1962b..d3b26a85a40d 100644 --- a/client/src/components/Workflow/Editor/NodeInput.vue +++ b/client/src/components/Workflow/Editor/NodeInput.vue @@ -70,6 +70,10 @@ const props = defineProps({ type: Boolean, default: false, }, + blank: { + type: Boolean, + default: false, + }, }); onBeforeUnmount(() => { @@ -145,7 +149,7 @@ const label = computed(() => props.input.label || props.input.name); const hasConnections = computed(() => connections.value.length > 0); const rowClass = computed(() => { const classes = ["form-row", "dataRow", "input-data-row"]; - if (props.input?.valid === false) { + if (!props.blank && props.input?.valid === false) { classes.push("form-row-error"); } return classes; @@ -234,21 +238,23 @@ watch(
- - {{ label }} - - * + + + {{ label }} + + * +
diff --git a/client/src/components/Workflow/Editor/NodeInvocationText.vue b/client/src/components/Workflow/Editor/NodeInvocationText.vue new file mode 100644 index 000000000000..acc3dd31f555 --- /dev/null +++ b/client/src/components/Workflow/Editor/NodeInvocationText.vue @@ -0,0 +1,39 @@ + + diff --git a/client/src/components/Workflow/Editor/NodeOutput.vue b/client/src/components/Workflow/Editor/NodeOutput.vue index cac8dba4a5ac..9e16d7fb236f 100644 --- a/client/src/components/Workflow/Editor/NodeOutput.vue +++ b/client/src/components/Workflow/Editor/NodeOutput.vue @@ -51,6 +51,7 @@ const props = defineProps<{ datatypesMapper: DatatypesMapperModel; parentNode: HTMLElement | null; readonly: boolean; + blank: boolean; }>(); const emit = defineEmits(["pan-by", "stopDragging", "onDragConnector"]); @@ -341,7 +342,7 @@ const removeTagsAction = computed(() => {