Skip to content

Commit

Permalink
Merge branch 'release_24.0' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
mvdbeek committed Apr 21, 2024
2 parents a5d6688 + 8f4968f commit 5097cb7
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 25 deletions.
4 changes: 3 additions & 1 deletion client/src/components/History/HistoryView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
</b-button>
</div>

<b-alert :show="copySuccess"> History imported and set to your active history. </b-alert>
<b-alert :show="copySuccess">
History imported and is now your active history. <b-link to="/histories/list">View here</b-link>.
</b-alert>

<CollectionPanel
v-if="selectedCollections.length && selectedCollections[0].history_id == id"
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/Tags/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { keyedColorScheme } from "utils/color";

// Valid tag regex. The basic format here is a tag name with optional subtags
// separated by a period, and then an optional value after a colon.
export const VALID_TAG_RE = /^([^\s.:])+(.[^\s.:]+)*(:[^\s.:]+)?$/;
export const VALID_TAG_RE = /^([^\s.:])+(\.[^\s.:]+)*(:\S+)?$/;

export class TagModel {
/**
Expand Down
37 changes: 24 additions & 13 deletions lib/galaxy/files/sources/_pyfilesystem2.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,14 @@
)

import fs
import fs.errors
from fs.base import FS
from typing_extensions import Unpack

from galaxy.exceptions import (
AuthenticationRequired,
MessageException,
)
from . import (
BaseFilesSource,
FilesSourceOptions,
Expand Down Expand Up @@ -42,19 +47,25 @@ def _open_fs(self, user_context=None, opts: Optional[FilesSourceOptions] = None)

def _list(self, path="/", recursive=False, user_context=None, opts: Optional[FilesSourceOptions] = None):
"""Return dictionary of 'Directory's and 'File's."""

with self._open_fs(user_context=user_context, opts=opts) as h:
if recursive:
res: List[Dict[str, Any]] = []
for p, dirs, files in h.walk(path):
to_dict = functools.partial(self._resource_info_to_dict, p)
res.extend(map(to_dict, dirs))
res.extend(map(to_dict, files))
return res
else:
res = h.scandir(path, namespaces=["details"])
to_dict = functools.partial(self._resource_info_to_dict, path)
return list(map(to_dict, res))
try:
with self._open_fs(user_context=user_context, opts=opts) as h:
if recursive:
res: List[Dict[str, Any]] = []
for p, dirs, files in h.walk(path):
to_dict = functools.partial(self._resource_info_to_dict, p)
res.extend(map(to_dict, dirs))
res.extend(map(to_dict, files))
return res
else:
res = h.scandir(path, namespaces=["details"])
to_dict = functools.partial(self._resource_info_to_dict, path)
return list(map(to_dict, res))
except fs.errors.PermissionDenied as e:
raise AuthenticationRequired(
f"Permission Denied. Reason: {e}. Please check your credentials in your preferences for {self.label}."
)
except fs.errors.FSError as e:
raise MessageException(f"Problem listing file source path {path}. Reason: {e}") from e

def _realize_to(self, source_path, native_path, user_context=None, opts: Optional[FilesSourceOptions] = None):
with open(native_path, "wb") as write_file:
Expand Down
17 changes: 15 additions & 2 deletions lib/galaxy/files/sources/dropbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
Union,
)

from galaxy.exceptions import (
AuthenticationRequired,
MessageException,
)
from . import (
FilesSourceOptions,
FilesSourceProperties,
Expand All @@ -27,8 +31,17 @@ def _open_fs(self, user_context=None, opts: Optional[FilesSourceOptions] = None)
if "accessToken" in props:
props["access_token"] = props.pop("accessToken")

handle = DropboxFS(**{**props, **extra_props})
return handle
try:
handle = DropboxFS(**{**props, **extra_props})
return handle
except Exception as e:
# This plugin might raise dropbox.dropbox_client.BadInputException
# which is not a subclass of fs.errors.FSError
if "OAuth2" in str(e):
raise AuthenticationRequired(
f"Permission Denied. Reason: {e}. Please check your credentials in your preferences for {self.label}."
)
raise MessageException(f"Error connecting to Dropbox. Reason: {e}")


__all__ = ("DropboxFilesSource",)
11 changes: 9 additions & 2 deletions lib/galaxy/managers/remote_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from galaxy import exceptions
from galaxy.files import (
ConfiguredFileSources,
FileSourcePath,
ProvidesUserFileSourcesUserContext,
)
from galaxy.files.sources import (
Expand Down Expand Up @@ -94,10 +95,10 @@ def index(
opts=opts,
)
except exceptions.MessageException:
log.warning(f"Problem listing file source path {file_source_path}", exc_info=True)
log.warning(self._get_error_message(file_source_path), exc_info=True)
raise
except Exception:
message = f"Problem listing file source path {file_source_path}"
message = self._get_error_message(file_source_path)
log.warning(message, exc_info=True)
raise exceptions.InternalServerError(message)
if format == RemoteFilesFormat.flat:
Expand Down Expand Up @@ -131,6 +132,9 @@ def index(

return index

def _get_error_message(self, file_source_path: FileSourcePath) -> str:
return f"Problem listing file source path {file_source_path.file_source.get_uri_root()}{file_source_path.path}"

def get_files_source_plugins(
self,
user_context: ProvidesUserContext,
Expand Down Expand Up @@ -162,6 +166,9 @@ def create_entry(self, user_ctx: ProvidesUserContext, entry_data: CreateEntryPay
file_source = file_source_path.file_source
try:
result = file_source.create_entry(entry_data.dict(), user_context=user_file_source_context)
except exceptions.MessageException:
log.warning(f"Problem creating entry {entry_data.name} in file source {entry_data.target}", exc_info=True)
raise
except Exception:
message = f"Problem creating entry {entry_data.name} in file source {entry_data.target}"
log.warning(message, exc_info=True)
Expand Down
9 changes: 8 additions & 1 deletion lib/galaxy/model/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,14 @@ def get_tag_by_name(self, tag_name):
return None

def _create_tag(self, tag_str: str):
"""Create a Tag object from a tag string."""
"""
Create or retrieve one or more Tag objects from a tag string. If there are multiple
hierarchical tags in the tag string, the string will be split along `self.hierarchy_separator` chars.
A Tag instance will be created for each non-empty prefix. If a prefix corresponds to the
name of an existing tag, that tag will be retrieved; otherwise, a new Tag object will be created.
For example, for the tag string `a.b.c` 3 Tag instances will be created: `a`, `a.b`, `a.b.c`.
Return the last tag created (`a.b.c`).
"""
tag_hierarchy = tag_str.split(self.hierarchy_separator)
tag_prefix = ""
parent_tag = None
Expand Down
4 changes: 3 additions & 1 deletion lib/galaxy/schema/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@

OptionalNumberT = Annotated[Optional[Union[int, float]], Field(None)]

TAG_ITEM_PATTERN = r"^([^\s.:])+(\.[^\s.:]+)*(:\S+)?$"


class DatasetState(str, Enum):
NEW = "new"
Expand Down Expand Up @@ -527,7 +529,7 @@ class HistoryContentSource(str, Enum):
DatasetCollectionInstanceType = Literal["history", "library"]


TagItem = Annotated[str, Field(..., pattern=r"^([^\s.:])+(.[^\s.:]+)*(:[^\s.:]+)?$")]
TagItem = Annotated[str, Field(..., pattern=TAG_ITEM_PATTERN)]


class TagCollection(RootModel):
Expand Down
6 changes: 3 additions & 3 deletions lib/galaxy_test/api/test_item_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,9 @@ def _create_valid_tag(self, prefix: str):
return response

def _create_history_contents(self, history_id):
history_content_id = self.dataset_collection_populator.create_list_in_history(
history_id, contents=["test_dataset"], direct_upload=True, wait=True
).json()["outputs"][0]["id"]
history_content_id = self.dataset_populator.new_dataset(
history_id, contents="test_dataset", direct_upload=True, wait=True
)["id"]
return history_content_id

def _create_history(self):
Expand Down
12 changes: 12 additions & 0 deletions test/unit/app/managers/test_TagHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,15 @@ def test_item_has_tag(self):
# Tag
assert self.tag_handler.item_has_tag(self.user, item=hda, tag=hda.tags[0].tag)
assert not self.tag_handler.item_has_tag(self.user, item=hda, tag="tag2")

def test_get_name_value_pair(self):
"""Verify that parsing a single tag string correctly splits it into name/value pairs."""
assert self.tag_handler.parse_tags("a") == [("a", None)]
assert self.tag_handler.parse_tags("a.b") == [("a.b", None)]
assert self.tag_handler.parse_tags("a.b:c") == [("a.b", "c")]
assert self.tag_handler.parse_tags("a.b:c.d") == [("a.b", "c.d")]
assert self.tag_handler.parse_tags("a.b:c.d:e.f") == [("a.b", "c.d:e.f")]
assert self.tag_handler.parse_tags("a.b:c.d:e.f.") == [("a.b", "c.d:e.f.")]
assert self.tag_handler.parse_tags("a.b:c.d:e.f..") == [("a.b", "c.d:e.f..")]
assert self.tag_handler.parse_tags("a.b:c.d:e.f:") == [("a.b", "c.d:e.f:")]
assert self.tag_handler.parse_tags("a.b:c.d:e.f::") == [("a.b", "c.d:e.f::")]
46 changes: 45 additions & 1 deletion test/unit/schema/test_schema.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import re
from uuid import uuid4

from pydantic import BaseModel

from galaxy.schema.schema import DatasetStateField
from galaxy.schema.schema import (
DatasetStateField,
TAG_ITEM_PATTERN,
)
from galaxy.schema.tasks import (
GenerateInvocationDownload,
RequestUser,
Expand Down Expand Up @@ -34,3 +38,43 @@ class StateModel(BaseModel):
def test_dataset_state_coercion():
assert StateModel(state="ok").state == "ok"
assert StateModel(state="deleted").state == "discarded"


class TestTagPattern:

def test_valid(self):
tag_strings = [
"a",
"aa",
"aa.aa",
"aa.aa.aa",
"~!@#$%^&*()_+`-=[]{};'\",./<>?",
"a.b:c",
"a.b:c.d:e.f",
"a.b:c.d:e..f",
"a.b:c.d:e.f:g",
"a.b:c.d:e.f::g",
"a.b:c.d:e.f::g:h",
"a::a", # leading colon for tag value
"a:.a", # leading period for tag value
"a:a:", # trailing colon OK for tag value
"a:a.", # trailing period OK for tag value
]
for t in tag_strings:
assert re.match(TAG_ITEM_PATTERN, t)

def test_invalid(self):
tag_strings = [
" a", # leading space for tag name
":a", # leading colon for tag name
".a", # leading period for tag name
"a ", # trailing space for tag name
"a a", # space inside tag name
"a: a", # leading space for tag value
"a:a a", # space inside tag value
"a:", # trailing colon for tag name
"a.", # trailing period for tag name
"a:b ", # trailing space for tag value
]
for t in tag_strings:
assert not re.match(TAG_ITEM_PATTERN, t)

0 comments on commit 5097cb7

Please sign in to comment.