diff --git a/.github/workflows/toolshed.yaml b/.github/workflows/toolshed.yaml
index cc54bf43c067..f6c06663da3c 100644
--- a/.github/workflows/toolshed.yaml
+++ b/.github/workflows/toolshed.yaml
@@ -20,14 +20,23 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: ['3.7']
- test-install-client: ['standalone', 'galaxy_api']
- # v1 is mostly working...
- shed-api: ['v1']
- # lets get twill working with twill then try to
- # make progress on the playwright
- # shed-browser: ['twill', 'playwright']
- shed-browser: ['playwright']
+ include:
+ - test-install-client: 'galaxy_api'
+ python-version: '3.7'
+ shed-api: 'v1'
+ shed-browser: 'twill'
+ - test-install-client: 'standalone'
+ python-version: '3.8'
+ shed-api: 'v1'
+ shed-browser: 'twill'
+ - test-install-client: 'galaxy_api'
+ python-version: '3.9'
+ shed-api: 'v2'
+ shed-browser: 'playwright'
+ - test-install-client: 'standalone'
+ python-version: '3.10'
+ shed-api: 'v2'
+ shed-browser: 'playwright'
services:
postgres:
image: postgres:13
diff --git a/.vscode/shed.code-snippets b/.vscode/shed.code-snippets
new file mode 100644
index 000000000000..f90a37c71313
--- /dev/null
+++ b/.vscode/shed.code-snippets
@@ -0,0 +1,41 @@
+{
+ "shedcomp": {
+ "prefix": "shed_component",
+ "body": [
+ "",
+ "",
+ ""
+ ],
+ "description": "outline of a tool shed component"
+ },
+ "shedpage": {
+ "prefix": "shed_page",
+ "body": [
+ "",
+ "",
+ " ",
+ " $0",
+ " ",
+ ""
+ ],
+ "description": "outline of a tool shed page"
+ },
+ "shedfetcher": {
+ "prefix": "shed_fetcher",
+ "body": [
+ "import { fetcher } from \"@/schema\"",
+ "const fetcher = fetcher.path(\"$1\").method(\"get\").create()"
+ ],
+ "description": "Import shed fetcher and instantiate with a path"
+ },
+ "shedrouter": {
+ "prefix": "shed_router",
+ "body": [
+ "import router from \"@/router\""
+ ]
+ }
+}
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 0dd02e253c9d..f20548df435e 100644
--- a/Makefile
+++ b/Makefile
@@ -190,6 +190,7 @@ remove-api-schema:
update-client-api-schema: client-node-deps build-api-schema
$(IN_VENV) cd client && node openapi_to_schema.mjs ../_schema.yaml > src/schema/schema.ts && npx prettier --write src/schema/schema.ts
+ $(IN_VENV) cd client && node openapi_to_schema.mjs ../_shed_schema.yaml > ../lib/tool_shed/webapp/frontend/src/schema/schema.ts && npx prettier --write ../lib/tool_shed/webapp/frontend/src/schema/schema.ts
$(MAKE) remove-api-schema
lint-api-schema: build-api-schema
diff --git a/lib/galaxy/dependencies/pinned-requirements.txt b/lib/galaxy/dependencies/pinned-requirements.txt
index 4b851aed081a..dcc9ccb09e8c 100644
--- a/lib/galaxy/dependencies/pinned-requirements.txt
+++ b/lib/galaxy/dependencies/pinned-requirements.txt
@@ -73,6 +73,7 @@ fsspec==2023.1.0 ; python_version >= "3.7" and python_version < "3.12"
future==0.18.3 ; python_version >= "3.7" and python_version < "3.12"
galaxy-sequence-utils==1.1.5 ; python_version >= "3.7" and python_version < "3.12"
galaxy2cwl==0.1.4 ; python_version >= "3.7" and python_version < "3.12"
+graphene-sqlalchemy==3.0.0b3 ; python_version >= "3.7" and python_version < "3.12"
gravity==1.0.3 ; python_version >= "3.7" and python_version < "3.12"
greenlet==2.0.2 ; python_version >= "3.7" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") and python_version < "3.12"
gunicorn==21.2.0 ; python_version >= "3.7" and python_version < "3.12"
@@ -179,6 +180,7 @@ sqlalchemy==1.4.49 ; python_version >= "3.7" and python_version < "3.12"
sqlitedict==2.1.0 ; python_version >= "3.7" and python_version < "3.12"
sqlparse==0.4.4 ; python_version >= "3.7" and python_version < "3.12"
starlette-context==0.3.5 ; python_version >= "3.7" and python_version < "3.12"
+starlette_graphene3==0.6.0 ; python_version >= "3.7" and python_version < "3.12"
starlette==0.27.0 ; python_version >= "3.7" and python_version < "3.12"
supervisor==4.2.5 ; python_version >= "3.7" and python_version < "3.12"
svgwrite==1.4.3 ; python_version >= "3.7" and python_version < "3.12"
diff --git a/lib/galaxy/managers/api_keys.py b/lib/galaxy/managers/api_keys.py
index 8e9b41c32380..c5584acc158b 100644
--- a/lib/galaxy/managers/api_keys.py
+++ b/lib/galaxy/managers/api_keys.py
@@ -3,7 +3,8 @@
TYPE_CHECKING,
)
-from galaxy.model import User
+from typing_extensions import Protocol
+
from galaxy.model.base import transaction
from galaxy.structured_app import BasicSharedApp
@@ -11,11 +12,15 @@
from galaxy.model import APIKeys
+class IsUserModel(Protocol):
+ id: str
+
+
class ApiKeyManager:
def __init__(self, app: BasicSharedApp):
self.app = app
- def get_api_key(self, user: User) -> Optional["APIKeys"]:
+ def get_api_key(self, user: IsUserModel) -> Optional["APIKeys"]:
sa_session = self.app.model.context
api_key = (
sa_session.query(self.app.model.APIKeys)
@@ -25,7 +30,7 @@ def get_api_key(self, user: User) -> Optional["APIKeys"]:
)
return api_key
- def create_api_key(self, user: User) -> "APIKeys":
+ def create_api_key(self, user: IsUserModel) -> "APIKeys":
guid = self.app.security.get_new_guid()
new_key = self.app.model.APIKeys()
new_key.user_id = user.id
@@ -36,7 +41,7 @@ def create_api_key(self, user: User) -> "APIKeys":
sa_session.commit()
return new_key
- def get_or_create_api_key(self, user: User) -> str:
+ def get_or_create_api_key(self, user: IsUserModel) -> str:
# Logic Galaxy has always used - but it would appear to have a race
# condition. Worth fixing? Would kind of need a message queue to fix
# in multiple process mode.
@@ -44,7 +49,7 @@ def get_or_create_api_key(self, user: User) -> str:
key = api_key.key if api_key else self.create_api_key(user).key
return key
- def delete_api_key(self, user: User) -> None:
+ def delete_api_key(self, user: IsUserModel) -> None:
"""Marks the current user API key as deleted."""
sa_session = self.app.model.context
# Before it was possible to create multiple API keys for the same user although they were not considered valid
diff --git a/lib/galaxy/managers/users.py b/lib/galaxy/managers/users.py
index 4489a00689e6..af50f93d7e1b 100644
--- a/lib/galaxy/managers/users.py
+++ b/lib/galaxy/managers/users.py
@@ -451,7 +451,9 @@ def change_password(self, trans, password=None, confirm=None, token=None, id=Non
trans.sa_session.add(token_result)
return user, "Password has been changed. Token has been invalidated."
else:
- user = self.by_id(self.app.security.decode_id(id))
+ if not isinstance(id, int):
+ id = self.app.security.decode_id(id)
+ user = self.by_id(id)
if user:
message = self.app.auth_manager.check_change_password(user, current, trans.request)
if message:
diff --git a/lib/galaxy/webapps/base/webapp.py b/lib/galaxy/webapps/base/webapp.py
index 8a4e4af3dd73..d56a2d878a55 100644
--- a/lib/galaxy/webapps/base/webapp.py
+++ b/lib/galaxy/webapps/base/webapp.py
@@ -735,21 +735,9 @@ def __create_new_session(self, prev_galaxy_session=None, user_for_new_session=No
Caller is responsible for flushing the returned session.
"""
- session_key = self.security.get_new_guid()
- galaxy_session = self.app.model.GalaxySession(
- session_key=session_key,
- is_valid=True,
- remote_host=self.request.remote_host,
- remote_addr=self.request.remote_addr,
- referer=self.request.headers.get("Referer", None),
+ return create_new_session(
+ self, prev_galaxy_session=prev_galaxy_session, user_for_new_session=user_for_new_session
)
- if prev_galaxy_session:
- # Invalidated an existing session for some reason, keep track
- galaxy_session.prev_session_id = prev_galaxy_session.id
- if user_for_new_session:
- # The new session should be associated with the user
- galaxy_session.user = user_for_new_session
- return galaxy_session
@property
def cookie_path(self):
@@ -1106,6 +1094,31 @@ def qualified_url_for_path(self, path):
return url_for(path, qualified=True)
+def create_new_session(trans, prev_galaxy_session=None, user_for_new_session=None):
+ """
+ Create a new GalaxySession for this request, possibly with a connection
+ to a previous session (in `prev_galaxy_session`) and an existing user
+ (in `user_for_new_session`).
+
+ Caller is responsible for flushing the returned session.
+ """
+ session_key = trans.security.get_new_guid()
+ galaxy_session = trans.app.model.GalaxySession(
+ session_key=session_key,
+ is_valid=True,
+ remote_host=trans.request.remote_host,
+ remote_addr=trans.request.remote_addr,
+ referer=trans.request.headers.get("Referer", None),
+ )
+ if prev_galaxy_session:
+ # Invalidated an existing session for some reason, keep track
+ galaxy_session.prev_session_id = prev_galaxy_session.id
+ if user_for_new_session:
+ # The new session should be associated with the user
+ galaxy_session.user = user_for_new_session
+ return galaxy_session
+
+
def default_url_path(path):
return os.path.abspath(os.path.join(os.path.dirname(__file__), path))
diff --git a/lib/galaxy/webapps/galaxy/api/__init__.py b/lib/galaxy/webapps/galaxy/api/__init__.py
index b38ef704a398..b6a44a1f43ac 100644
--- a/lib/galaxy/webapps/galaxy/api/__init__.py
+++ b/lib/galaxy/webapps/galaxy/api/__init__.py
@@ -47,6 +47,7 @@
NoMatchFound,
)
from starlette.types import Scope
+from typing_extensions import Literal
try:
from starlette_context import context as request_context
@@ -200,6 +201,8 @@ class GalaxyASGIRequest(GalaxyAbstractRequest):
Implements the GalaxyAbstractRequest interface to provide access to some properties
of the request commonly used."""
+ __request: Request
+
def __init__(self, request: Request):
self.__request = request
self.__environ: Optional[MutableMapping[str, Any]] = None
@@ -232,6 +235,28 @@ def environ(self) -> MutableMapping[str, Any]:
self.__environ = build_environ(self.__request.scope, None) # type: ignore[arg-type]
return self.__environ
+ @property
+ def headers(self):
+ return self.__request.headers
+
+ @property
+ def remote_host(self) -> str:
+ # was available in wsgi and is used create_new_session
+ return self.host
+
+ @property
+ def remote_addr(self) -> Optional[str]:
+ # was available in wsgi and is used create_new_session
+ # not sure what to do here...
+ return None
+
+ @property
+ def is_secure(self) -> bool:
+ return self.__request.url.scheme == "https"
+
+ def get_cookie(self, name):
+ return self.__request.cookies.get(name)
+
class GalaxyASGIResponse(GalaxyAbstractResponse):
"""Wrapper around Starlette/FastAPI Response object.
@@ -246,6 +271,31 @@ def __init__(self, response: Response):
def headers(self):
return self.__response.headers
+ def set_cookie(
+ self,
+ key: str,
+ value: str = "",
+ max_age: Optional[int] = None,
+ expires: Optional[int] = None,
+ path: str = "/",
+ domain: Optional[str] = None,
+ secure: bool = False,
+ httponly: bool = False,
+ samesite: Optional[Literal["lax", "strict", "none"]] = "lax",
+ ) -> None:
+ """Set a cookie."""
+ self.__response.set_cookie(
+ key,
+ value,
+ max_age=max_age,
+ expires=expires,
+ path=path,
+ domain=domain,
+ secure=secure,
+ httponly=httponly,
+ samesite=samesite,
+ )
+
DependsOnUser = cast(Optional[User], Depends(get_user))
diff --git a/lib/galaxy/webapps/galaxy/controllers/user.py b/lib/galaxy/webapps/galaxy/controllers/user.py
index 193255a0b59a..fb9d7e11c5cf 100644
--- a/lib/galaxy/webapps/galaxy/controllers/user.py
+++ b/lib/galaxy/webapps/galaxy/controllers/user.py
@@ -188,7 +188,7 @@ def __validate_login(self, trans, payload=None, **kwd):
message, status = self.resend_activation_email(trans, user.email, user.username)
return self.message_exception(trans, message, sanitize=False)
else: # activation is OFF
- pw_expires = trans.app.config.password_expiration_period
+ pw_expires = getattr(trans.app.config, "password_expiration_period", None)
if pw_expires and user.last_password_change < datetime.today() - pw_expires:
# Password is expired, we don't log them in.
return {
diff --git a/lib/galaxy/work/context.py b/lib/galaxy/work/context.py
index 310779e504e3..8a1206018c0a 100644
--- a/lib/galaxy/work/context.py
+++ b/lib/galaxy/work/context.py
@@ -4,6 +4,8 @@
Optional,
)
+from typing_extensions import Literal
+
from galaxy.managers.context import ProvidesHistoryContext
from galaxy.model import (
GalaxySession,
@@ -85,6 +87,14 @@ def base(self) -> str:
def host(self) -> str:
"""The host address."""
+ @abc.abstractproperty
+ def is_secure(self) -> bool:
+ """Was this a secure (https) request."""
+
+ @abc.abstractmethod
+ def get_cookie(self, name):
+ """Return cookie."""
+
class GalaxyAbstractResponse:
"""Abstract interface to provide access to some response utilities."""
@@ -102,6 +112,21 @@ def set_content_type(self, content_type: str):
def get_content_type(self):
return self.headers.get("content-type", None)
+ @abc.abstractmethod
+ def set_cookie(
+ self,
+ key: str,
+ value: str = "",
+ max_age: Optional[int] = None,
+ expires: Optional[int] = None,
+ path: str = "/",
+ domain: Optional[str] = None,
+ secure: bool = False,
+ httponly: bool = False,
+ samesite: Optional[Literal["lax", "strict", "none"]] = "lax",
+ ) -> None:
+ """Set a cookie."""
+
class SessionRequestContext(WorkRequestContext):
"""Like WorkRequestContext, but provides access to request."""
diff --git a/lib/tool_shed/context.py b/lib/tool_shed/context.py
index 6991bbd112cd..107be8c8ed48 100644
--- a/lib/tool_shed/context.py
+++ b/lib/tool_shed/context.py
@@ -84,6 +84,10 @@ class SessionRequestContext(ProvidesRepositoriesContext, Protocol):
def get_galaxy_session(self) -> Optional[GalaxySession]:
...
+ @abc.abstractmethod
+ def set_galaxy_session(self, galaxy_session: GalaxySession):
+ ...
+
@abc.abstractproperty
def request(self) -> GalaxyAbstractRequest:
...
@@ -96,6 +100,10 @@ def response(self) -> GalaxyAbstractResponse:
def url_builder(self):
...
+ @abc.abstractproperty
+ def session_csrf_token(self) -> str:
+ ...
+
class SessionRequestContextImpl(SessionRequestContext):
_app: ToolShedApp
@@ -133,6 +141,11 @@ def user(self) -> Optional[User]:
def get_galaxy_session(self) -> Optional[GalaxySession]:
return self._galaxy_session
+ def set_galaxy_session(self, galaxy_session: GalaxySession):
+ self._galaxy_session = galaxy_session
+ if galaxy_session.user:
+ self._user = galaxy_session.user
+
@property
def repositories_hostname(self) -> str:
return str(self.request.base).rstrip("/")
@@ -148,3 +161,18 @@ def request(self) -> GalaxyAbstractRequest:
@property
def response(self) -> GalaxyAbstractResponse:
return self.__response
+
+ # Following three things added v2.0 frontend
+ @property
+ def session_csrf_token(self):
+ token = ""
+ if self._galaxy_session:
+ token = self.security.encode_id(self._galaxy_session.id, kind="csrf")
+ return token
+
+ @property
+ def galaxy_session(self) -> Optional[GalaxySession]:
+ return self._galaxy_session
+
+ def log_event(self, str):
+ pass
diff --git a/lib/tool_shed/managers/repositories.py b/lib/tool_shed/managers/repositories.py
index f72e61fa1a31..805f716164c5 100644
--- a/lib/tool_shed/managers/repositories.py
+++ b/lib/tool_shed/managers/repositories.py
@@ -433,6 +433,7 @@ def get_repository_metadata_dict(app: ToolShedApp, id: str, recursive: bool, dow
metadata_dict["repository_dependencies"] = []
if metadata.includes_tools:
metadata_dict["tools"] = metadata.metadata["tools"]
+ metadata_dict["invalid_tools"] = metadata.metadata.get("invalid_tools", [])
all_metadata[f"{int(changeset)}:{changehash}"] = metadata_dict
return all_metadata
diff --git a/lib/tool_shed/test/base/playwrightbrowser.py b/lib/tool_shed/test/base/playwrightbrowser.py
index 6d00f794c69b..d29529cece24 100644
--- a/lib/tool_shed/test/base/playwrightbrowser.py
+++ b/lib/tool_shed/test/base/playwrightbrowser.py
@@ -13,6 +13,13 @@
)
+class Locators:
+ toolbar_login = ".toolbar-login"
+ toolbar_logout = ".toolbar-logout"
+ login_submit_button = '[name="login_button"]'
+ register_link = ".register-link"
+
+
class PlaywrightShedBrowser(ShedBrowser):
_page: Page
@@ -151,3 +158,17 @@ def grant_users_access(self, usernames: List[str]):
@property
def is_twill(self) -> bool:
return False
+
+ def logout_if_logged_in(self, assert_logged_out=True):
+ self._page.wait_for_selector(f"{Locators.toolbar_login}, {Locators.toolbar_logout}")
+ logout_locator = self._page.locator(Locators.toolbar_logout)
+ if logout_locator.is_visible():
+ logout_locator.click()
+ if assert_logged_out:
+ self.expect_not_logged_in()
+
+ def expect_not_logged_in(self):
+ expect(self._page.locator(Locators.toolbar_logout)).not_to_be_visible()
+
+ def expect_logged_in(self):
+ expect(self._page.locator(Locators.toolbar_logout)).to_be_visible()
diff --git a/lib/tool_shed/test/base/twilltestcase.py b/lib/tool_shed/test/base/twilltestcase.py
index 5eca1b0543f2..8c15af3dc210 100644
--- a/lib/tool_shed/test/base/twilltestcase.py
+++ b/lib/tool_shed/test/base/twilltestcase.py
@@ -29,6 +29,7 @@
hg,
ui,
)
+from playwright.sync_api import Page
from sqlalchemy import (
and_,
false,
@@ -74,6 +75,7 @@
)
from .api import ShedApiTestCase
from .browser import ShedBrowser
+from .playwrightbrowser import PlaywrightShedBrowser
from .twillbrowser import (
page_content,
visit_url,
@@ -692,39 +694,52 @@ def check_string_not_in_page(self, patt):
self._browser.check_string_not_in_page(patt)
# Functions associated with user accounts
+ def _submit_register_form(self, email: str, password: str, username: str, redirect: Optional[str] = None):
+ self._browser.fill_form_value("registration", "email", email)
+ if redirect is not None:
+ self._browser.fill_form_value("registration", "redirect", redirect)
+ self._browser.fill_form_value("registration", "password", password)
+ self._browser.fill_form_value("registration", "confirm", password)
+ self._browser.fill_form_value("registration", "username", username)
+ self._browser.submit_form_with_name("registration", "create_user_button")
+
+ @property
+ def invalid_tools_labels(self) -> str:
+ return "Invalid Tools" if self.is_v2 else "Invalid tools"
def create(self, cntrller="user", email="test@bx.psu.edu", password="testuser", username="admin-user", redirect=""):
# HACK: don't use panels because late_javascripts() messes up the twill browser and it
# can't find form fields (and hence user can't be logged in).
params = dict(cntrller=cntrller, use_panels=False)
self.visit_url("/user/create", params)
- self._browser.fill_form_value("registration", "email", email)
- self._browser.fill_form_value("registration", "redirect", redirect)
- self._browser.fill_form_value("registration", "password", password)
- self._browser.fill_form_value("registration", "confirm", password)
- self._browser.fill_form_value("registration", "username", username)
- self._browser.submit_form_with_name("registration", "create_user_button")
+ self._submit_register_form(
+ email,
+ password,
+ username,
+ redirect,
+ )
previously_created = False
username_taken = False
invalid_username = False
- try:
- self.check_page_for_string("Created new user account")
- except AssertionError:
+ if not self.is_v2:
try:
- # May have created the account in a previous test run...
- self.check_page_for_string(f"User with email '{email}' already exists.")
- previously_created = True
+ self.check_page_for_string("Created new user account")
except AssertionError:
try:
- self.check_page_for_string("Public name is taken; please choose another")
- username_taken = True
+ # May have created the account in a previous test run...
+ self.check_page_for_string(f"User with email '{email}' already exists.")
+ previously_created = True
except AssertionError:
- # Note that we're only checking if the usr name is >< 4 chars here...
try:
- self.check_page_for_string("Public name must be at least 4 characters in length")
- invalid_username = True
+ self.check_page_for_string("Public name is taken; please choose another")
+ username_taken = True
except AssertionError:
- pass
+ # Note that we're only checking if the usr name is >< 4 chars here...
+ try:
+ self.check_page_for_string("Public name must be at least 4 characters in length")
+ invalid_username = True
+ except AssertionError:
+ pass
return previously_created, username_taken, invalid_username
def last_page(self):
@@ -748,6 +763,11 @@ def login(
redirect: str = "",
logout_first: bool = True,
):
+ if self.is_v2:
+ # old version had a logout URL, this one needs to check
+ # page if logged in
+ self.visit_url("/")
+
# Clear cookies.
if logout_first:
self.logout()
@@ -755,7 +775,8 @@ def login(
previously_created, username_taken, invalid_username = self.create(
email=email, password=password, username=username, redirect=redirect
)
- if previously_created:
+ # v2 doesn't log you in on account creation... so force a login here
+ if previously_created or self.is_v2:
# The acount has previously been created, so just login.
# HACK: don't use panels because late_javascripts() messes up the twill browser and it
# can't find form fields (and hence user can't be logged in).
@@ -763,9 +784,27 @@ def login(
self.visit_url("/user/login", params=params)
self.submit_form(button="login_button", login=email, redirect=redirect, password=password)
+ @property
+ def is_v2(self) -> bool:
+ return self.api_interactor.api_version == "v2"
+
+ @property
+ def _playwright_browser(self) -> PlaywrightShedBrowser:
+ # make sure self.is_v2
+ browser = self._browser
+ assert isinstance(browser, PlaywrightShedBrowser)
+ return browser
+
+ @property
+ def _page(self) -> Page:
+ return self._playwright_browser._page
+
def logout(self):
- self.visit_url("/user/logout")
- self.check_page_for_string("You have been logged out")
+ if self.is_v2:
+ self._playwright_browser.logout_if_logged_in()
+ else:
+ self.visit_url("/user/logout")
+ self.check_page_for_string("You have been logged out")
def submit_form(self, form_no=-1, button="runtool_btn", form=None, **kwd):
"""Populates and submits a form from the keyword arguments."""
@@ -816,12 +855,15 @@ def assign_admin_role(self, repository: Repository, user):
self.check_for_strings(strings_displayed=["Role", "has been associated"])
def browse_category(self, category: Category, strings_displayed=None, strings_not_displayed=None):
- params = {
- "sort": "name",
- "operation": "valid_repositories_by_category",
- "id": category.id,
- }
- self.visit_url("/repository/browse_valid_categories", params=params)
+ if self.is_v2:
+ self.visit_url(f"/repositories_by_category/{category.id}")
+ else:
+ params = {
+ "sort": "name",
+ "operation": "valid_repositories_by_category",
+ "id": category.id,
+ }
+ self.visit_url("/repository/browse_valid_categories", params=params)
self.check_for_strings(strings_displayed, strings_not_displayed)
def browse_repository(self, repository: Repository, strings_displayed=None, strings_not_displayed=None):
@@ -835,7 +877,10 @@ def browse_repository_dependencies(self, strings_displayed=None, strings_not_dis
self.check_for_strings(strings_displayed, strings_not_displayed)
def browse_tool_shed(self, url, strings_displayed=None, strings_not_displayed=None):
- url = "/repository/browse_valid_categories"
+ if self.is_v2:
+ url = "/repositories_by_category"
+ else:
+ url = "/repository/browse_valid_categories"
self.visit_url(url)
self.check_for_strings(strings_displayed, strings_not_displayed)
@@ -875,12 +920,14 @@ def check_repository_changelog(self, repository: Repository, strings_displayed=N
def check_repository_dependency(
self, repository: Repository, depends_on_repository, depends_on_changeset_revision=None, changeset_revision=None
):
- strings_displayed = [depends_on_repository.name, depends_on_repository.owner]
- if depends_on_changeset_revision:
- strings_displayed.append(depends_on_changeset_revision)
- self.display_manage_repository_page(
- repository, changeset_revision=changeset_revision, strings_displayed=strings_displayed
- )
+ if not self.is_v2:
+ # v2 doesn't display repository repository dependencies, they are deprecated
+ strings_displayed = [depends_on_repository.name, depends_on_repository.owner]
+ if depends_on_changeset_revision:
+ strings_displayed.append(depends_on_changeset_revision)
+ self.display_manage_repository_page(
+ repository, changeset_revision=changeset_revision, strings_displayed=strings_displayed
+ )
def check_repository_metadata(self, repository: Repository, tip_only=True):
if tip_only:
@@ -1176,7 +1223,10 @@ def display_manage_repository_page(
params = {"id": repository.id}
if changeset_revision:
params["changeset_revision"] = changeset_revision
- self.visit_url("/repository/manage_repository", params=params)
+ url = "/repository/manage_repository"
+ if self.is_v2:
+ url = f"/repositories/{repository.id}"
+ self.visit_url(url, params=params)
self.check_for_strings(strings_displayed, strings_not_displayed)
def display_repository_clone_page(
@@ -1592,17 +1642,20 @@ def load_citable_url(
url += f"/{changeset_revision}"
self.visit_url(url)
self.check_for_strings(strings_displayed, strings_not_displayed)
- # Now load the page that should be displayed inside the iframe and check for strings.
- if encoded_repository_id:
- params = {"id": encoded_repository_id, "operation": "view_or_manage_repository"}
- if changeset_revision:
- params["changeset_revision"] = changeset_revision
- self.visit_url("/repository/view_repository", params=params)
- self.check_for_strings(strings_displayed_in_iframe, strings_not_displayed_in_iframe)
- elif encoded_user_id:
- params = {"user_id": encoded_user_id, "operation": "repositories_by_user"}
- self.visit_url("/repository/browse_repositories", params=params)
+ if self.is_v2:
self.check_for_strings(strings_displayed_in_iframe, strings_not_displayed_in_iframe)
+ else:
+ # Now load the page that should be displayed inside the iframe and check for strings.
+ if encoded_repository_id:
+ params = {"id": encoded_repository_id, "operation": "view_or_manage_repository"}
+ if changeset_revision:
+ params["changeset_revision"] = changeset_revision
+ self.visit_url("/repository/view_repository", params=params)
+ self.check_for_strings(strings_displayed_in_iframe, strings_not_displayed_in_iframe)
+ elif encoded_user_id:
+ params = {"user_id": encoded_user_id, "operation": "repositories_by_user"}
+ self.visit_url("/repository/browse_repositories", params=params)
+ self.check_for_strings(strings_displayed_in_iframe, strings_not_displayed_in_iframe)
def load_changeset_in_tool_shed(
self, repository_id, changeset_revision, strings_displayed=None, strings_not_displayed=None
@@ -1694,9 +1747,13 @@ def repository_is_new(self, repository: Repository) -> bool:
return tip_ctx.rev() < 0
def reset_metadata_on_selected_repositories(self, repository_ids):
- self.visit_url("/admin/reset_metadata_on_selected_repositories_in_tool_shed")
- kwd = dict(repository_ids=repository_ids)
- self.submit_form(button="reset_metadata_on_selected_repositories_button", **kwd)
+ if self.is_v2:
+ for repository_id in repository_ids:
+ self.populator.reset_metadata(repository_id)
+ else:
+ self.visit_url("/admin/reset_metadata_on_selected_repositories_in_tool_shed")
+ kwd = dict(repository_ids=repository_ids)
+ self.submit_form(button="reset_metadata_on_selected_repositories_button", **kwd)
def reset_metadata_on_installed_repositories(self, repositories):
assert self._installation_client
diff --git a/lib/tool_shed/test/functional/test_0000_basic_repository_features.py b/lib/tool_shed/test/functional/test_0000_basic_repository_features.py
index bb34b1aca3fb..c34727257c17 100644
--- a/lib/tool_shed/test/functional/test_0000_basic_repository_features.py
+++ b/lib/tool_shed/test/functional/test_0000_basic_repository_features.py
@@ -1,5 +1,6 @@
import logging
+from ..base.api import skip_if_api_v2
from ..base.twilltestcase import (
common,
ShedTwillTestCase,
@@ -21,6 +22,9 @@ def test_0000_initiate_users(self):
self.login(email=common.test_user_2_email, username=common.test_user_2_name)
self.login(email=common.admin_email, username=common.admin_username)
+ @skip_if_api_v2
+ # no replicating the functionality in tool shed 2.0, use Planemo
+ # to create repositories.
def test_0005_create_repository_without_categories(self):
"""Verify that a repository cannot be created unless at least one category has been defined."""
strings_displayed = ["No categories have been configured in this instance of the Galaxy Tool Shed"]
@@ -69,6 +73,7 @@ def test_0025_change_repository_category(self):
categories_to_remove=["Test 0000 Basic Repository Features 1"],
)
+ @skip_if_api_v2
def test_0030_grant_write_access(self):
"""Grant write access to another user"""
repository = self._get_repository_by_name_and_owner(repository_name, common.test_user_1_name)
@@ -120,6 +125,7 @@ def test_0040_verify_repository(self):
strings_displayed=strings,
)
+ @skip_if_api_v2
def test_0045_alter_repository_states(self):
"""Test toggling the malicious and deprecated repository flags."""
repository = self._get_repository_by_name_and_owner(repository_name, common.test_user_1_name)
@@ -147,13 +153,14 @@ def test_0045_alter_repository_states(self):
strings_displayed = ["Mark repository as deprecated", "Reset all repository metadata"]
self.display_manage_repository_page(repository, strings_displayed=strings_displayed)
+ @skip_if_api_v2
+ # probably not porting this functionality - just test
+ # with Twill for older UI and drop when that is all dropped
def test_0050_display_repository_tip_file(self):
"""Display the contents of filtering.xml in the repository tip revision"""
repository = self._get_repository_by_name_and_owner(repository_name, common.test_user_1_name)
assert repository
if self._browser.is_twill:
- # probably not porting this functionality - just test
- # with Twill for older UI and drop when that is all dropped
self.display_repository_file_contents(
repository=repository,
filename="filtering.xml",
@@ -167,9 +174,7 @@ def test_0055_upload_filtering_txt_file(self):
repository = self._get_repository_by_name_and_owner(repository_name, common.test_user_1_name)
self.add_file_to_repository(repository, "filtering/filtering_0000.txt")
expected = self._escape_page_content_if_needed("Readme file for filtering 1.1.0")
- self.display_manage_repository_page(
- repository, strings_displayed=[expected]
- )
+ self.display_manage_repository_page(repository, strings_displayed=[expected])
def test_0060_upload_filtering_test_data(self):
"""Upload filtering test data."""
@@ -197,7 +202,10 @@ def test_0070_verify_filtering_repository(self):
repository = self._get_repository_by_name_and_owner(repository_name, common.test_user_1_name)
tip = self.get_repository_tip(repository)
self.check_for_valid_tools(repository)
- strings_displayed = ["Select a revision"]
+ if self.is_v2:
+ strings_displayed = []
+ else:
+ strings_displayed = ["Select a revision"]
self.display_manage_repository_page(repository, strings_displayed=strings_displayed)
self.check_count_of_metadata_revisions_associated_with_repository(repository, metadata_count=2)
tool_guid = f"{self.url.replace('http://', '').rstrip('/')}/repos/user1/filtering_0000/Filter1/2.2.0"
@@ -222,9 +230,7 @@ def test_0075_upload_readme_txt_file(self):
repository = self._get_repository_by_name_and_owner(repository_name, common.test_user_1_name)
self.add_file_to_repository(repository, "readme.txt")
content = self._escape_page_content_if_needed("This is a readme file.")
- self.display_manage_repository_page(
- repository, strings_displayed=[content]
- )
+ self.display_manage_repository_page(repository, strings_displayed=[content])
# Verify that there is a different readme file for each metadata revision.
readme_content = self._escape_page_content_if_needed("Readme file for filtering 1.1.0")
self.display_manage_repository_page(
@@ -241,10 +247,9 @@ def test_0080_delete_readme_txt_file(self):
self.delete_files_from_repository(repository, filenames=["readme.txt"])
self.check_count_of_metadata_revisions_associated_with_repository(repository, metadata_count=2)
readme_content = self._escape_page_content_if_needed("Readme file for filtering 1.1.0")
- self.display_manage_repository_page(
- repository, strings_displayed=[readme_content]
- )
+ self.display_manage_repository_page(repository, strings_displayed=[readme_content])
+ @skip_if_api_v2 # not re-implemented in the UI, there are API tests though
def test_0085_search_for_valid_filter_tool(self):
"""Search for the filtering tool by tool ID, name, and version."""
repository = self._get_repository_by_name_and_owner(repository_name, common.test_user_1_name)
@@ -279,16 +284,20 @@ def test_0100_verify_reserved_username_handling(self):
self.login(email="baduser@bx.psu.edu", username="repos")
test_user_1 = self.test_db_util.get_user("baduser@bx.psu.edu")
assert test_user_1 is None, 'Creating user with public name "repos" succeeded.'
- error_message = (
- "The term 'repos' is a reserved word in the Tool Shed, so it cannot be used as a public user name."
- )
- self.check_for_strings(strings_displayed=[error_message])
+ if not self.is_v2:
+ # no longer use this terminology but the above test case ensures
+ # the important thing and caught a bug in v2
+ error_message = (
+ "The term 'repos' is a reserved word in the Tool Shed, so it cannot be used as a public user name."
+ )
+ self.check_for_strings(strings_displayed=[error_message])
def test_0105_contact_repository_owner(self):
""""""
# We no longer implement this.
pass
+ @skip_if_api_v2 # v2 doesn't implement repository deleting repositories
def test_0110_delete_filtering_repository(self):
"""Delete the filtering_0000 repository and verify that it no longer has any downloadable revisions."""
repository = self._get_repository_by_name_and_owner(repository_name, common.test_user_1_name)
@@ -303,6 +312,7 @@ def test_0110_delete_filtering_repository(self):
# Marking a repository as deleted should result in no metadata revisions being downloadable.
# assert True not in [metadata.downloadable for metadata in self._db_repository(repository).metadata_revisions]
+ @skip_if_api_v2 # v2 doesn't implement repository deleting repositories
def test_0115_undelete_filtering_repository(self):
"""Undelete the filtering_0000 repository and verify that it now has two downloadable revisions."""
repository = self._get_repository_by_name_and_owner(repository_name, common.test_user_1_name)
@@ -316,6 +326,7 @@ def test_0115_undelete_filtering_repository(self):
assert True in [metadata.downloadable for metadata in self._db_repository(repository).metadata_revisions]
assert len(self._db_repository(repository).downloadable_revisions) == 2
+ @skip_if_api_v2 # not re-implementing in tool shed 2.0
def test_0120_enable_email_notifications(self):
"""Enable email notifications for test user 2 on filtering_0000."""
# Log in as test_user_2
@@ -332,9 +343,7 @@ def test_0125_upload_new_readme_file(self):
# Upload readme.txt to the filtering_0000 repository and verify that it is now displayed.
self.add_file_to_repository(repository, "filtering/readme.txt")
content = self._escape_page_content_if_needed("These characters should not")
- self.display_manage_repository_page(
- repository, strings_displayed=[content]
- )
+ self.display_manage_repository_page(repository, strings_displayed=[content])
def test_0130_verify_handling_of_invalid_characters(self):
"""Load the above changeset in the change log and confirm that there is no server error displayed."""
diff --git a/lib/tool_shed/test/functional/test_0010_repository_with_tool_dependencies.py b/lib/tool_shed/test/functional/test_0010_repository_with_tool_dependencies.py
index 830e0d0022c7..4b9d27ae19d7 100644
--- a/lib/tool_shed/test/functional/test_0010_repository_with_tool_dependencies.py
+++ b/lib/tool_shed/test/functional/test_0010_repository_with_tool_dependencies.py
@@ -1,5 +1,6 @@
import os
+from ..base.api import skip_if_api_v2
from ..base.twilltestcase import (
common,
ShedTwillTestCase,
@@ -55,8 +56,11 @@ def test_0010_create_freebayes_repository_and_upload_tool_xml(self):
assert repository
strings_displayed = ["Metadata may have been defined", "This file requires an entry", "tool_data_table_conf"]
self.add_file_to_repository(repository, "freebayes/freebayes.xml", strings_displayed=strings_displayed)
+ if self.is_v2:
+ # opps... not good right?
+ self.populator.reset_metadata(repository)
self.display_manage_repository_page(
- repository, strings_displayed=["Invalid tools"], strings_not_displayed=["Valid tools"]
+ repository, strings_displayed=[self.invalid_tools_labels], strings_not_displayed=["Valid tools"]
)
tip = self.get_repository_tip(repository)
strings_displayed = ["requires an entry", "tool_data_table_conf.xml"]
@@ -74,7 +78,7 @@ def test_0015_upload_missing_tool_data_table_conf_file(self):
repository, "freebayes/tool_data_table_conf.xml.sample", strings_displayed=strings_displayed
)
self.display_manage_repository_page(
- repository, strings_displayed=["Invalid tools"], strings_not_displayed=["Valid tools"]
+ repository, strings_displayed=[self.invalid_tools_labels], strings_not_displayed=["Valid tools"]
)
tip = self.get_repository_tip(repository)
strings_displayed = ["refers to a file", "sam_fa_indices.loc"]
@@ -124,6 +128,7 @@ def test_0035_upload_valid_tool_dependency_xml(self):
target = os.path.join("freebayes", "tool_dependencies.xml")
self.add_file_to_repository(repository, target)
+ @skip_if_api_v2
def test_0040_verify_tool_dependencies(self):
"""Verify that the uploaded tool_dependencies.xml specifies the correct package versions.
@@ -132,7 +137,7 @@ def test_0040_verify_tool_dependencies(self):
"""
repository = self._get_repository_by_name_and_owner(repository_name, common.test_user_1_name)
strings_displayed = ["freebayes", "0.9.4_9696d0ce8a9", "samtools", "0.1.18", "Valid tools", "package"]
- strings_not_displayed = ["Invalid tools"]
+ strings_not_displayed = [self.invalid_tools_labels]
self.display_manage_repository_page(
repository, strings_displayed=strings_displayed, strings_not_displayed=strings_not_displayed
)
diff --git a/lib/tool_shed/test/functional/test_0020_basic_repository_dependencies.py b/lib/tool_shed/test/functional/test_0020_basic_repository_dependencies.py
index 47fe9b0a0b84..f4fce544de59 100644
--- a/lib/tool_shed/test/functional/test_0020_basic_repository_dependencies.py
+++ b/lib/tool_shed/test/functional/test_0020_basic_repository_dependencies.py
@@ -1,3 +1,4 @@
+from ..base.api import skip_if_api_v2
from ..base.twilltestcase import (
common,
ShedTwillTestCase,
@@ -70,6 +71,7 @@ def test_0025_generate_and_upload_repository_dependencies_xml(self):
repository=repository, repository_tuples=[repository_tuple], filepath=repository_dependencies_path
)
+ @skip_if_api_v2
def test_0030_verify_emboss_5_dependencies(self):
"""Verify that the emboss_5 repository now depends on the emboss_datatypes repository with correct name, owner, and changeset revision."""
repository = self._get_repository_by_name_and_owner(emboss_repository_name, common.test_user_1_name)
diff --git a/lib/tool_shed/test/functional/test_0030_repository_dependency_revisions.py b/lib/tool_shed/test/functional/test_0030_repository_dependency_revisions.py
index 3b9883eb439e..8436d84d8794 100644
--- a/lib/tool_shed/test/functional/test_0030_repository_dependency_revisions.py
+++ b/lib/tool_shed/test/functional/test_0030_repository_dependency_revisions.py
@@ -1,3 +1,4 @@
+from ..base.api import skip_if_api_v2
from ..base.twilltestcase import (
common,
ShedTwillTestCase,
@@ -161,6 +162,7 @@ def test_0045_generate_repository_dependency_on_emboss_6(self):
repository=emboss_repository, repository_tuples=[emboss_tuple], filepath=repository_dependencies_path
)
+ @skip_if_api_v2
def test_0050_verify_repository_dependency_revisions(self):
"""Verify that different metadata revisions of the emboss repository have different repository dependencies."""
repository = self._get_repository_by_name_and_owner(emboss_repository_name, common.test_user_1_name)
diff --git a/lib/tool_shed/test/functional/test_0040_repository_circular_dependencies.py b/lib/tool_shed/test/functional/test_0040_repository_circular_dependencies.py
index d0382b3de0cc..c1a5f0de3315 100644
--- a/lib/tool_shed/test/functional/test_0040_repository_circular_dependencies.py
+++ b/lib/tool_shed/test/functional/test_0040_repository_circular_dependencies.py
@@ -1,3 +1,4 @@
+from ..base.api import skip_if_api_v2
from ..base.twilltestcase import (
common,
ShedTwillTestCase,
@@ -129,6 +130,7 @@ def test_0035_verify_repository_metadata(self):
for repository in [freebayes_repository, filtering_repository]:
self.verify_unchanged_repository_metadata(repository)
+ @skip_if_api_v2
def test_0040_verify_tool_dependencies(self):
"""Verify that freebayes displays tool dependencies."""
repository = self._get_repository_by_name_and_owner(freebayes_repository_name, common.test_user_1_name)
diff --git a/lib/tool_shed/test/functional/test_0050_circular_dependencies_4_levels.py b/lib/tool_shed/test/functional/test_0050_circular_dependencies_4_levels.py
index bbddfa0c219e..c774cf278cf3 100644
--- a/lib/tool_shed/test/functional/test_0050_circular_dependencies_4_levels.py
+++ b/lib/tool_shed/test/functional/test_0050_circular_dependencies_4_levels.py
@@ -1,3 +1,4 @@
+from ..base.api import skip_if_api_v2
from ..base.twilltestcase import (
common,
ShedTwillTestCase,
@@ -245,9 +246,11 @@ def test_0045_verify_repository_dependencies(self):
self.check_repository_dependency(filtering_repository, emboss_repository)
for repository in [bismark_repository, emboss_repository, column_repository]:
self.check_repository_dependency(freebayes_repository, repository)
- strings_displayed = ["freebayes_0050 depends on freebayes_0050, emboss_0050, column_maker_0050."]
- self.display_manage_repository_page(freebayes_repository, strings_displayed=strings_displayed)
+ if not self.is_v2:
+ strings_displayed = ["freebayes_0050 depends on freebayes_0050, emboss_0050, column_maker_0050."]
+ self.display_manage_repository_page(freebayes_repository, strings_displayed=strings_displayed)
+ @skip_if_api_v2
def test_0050_verify_tool_dependencies(self):
"""Check that freebayes and emboss display tool dependencies."""
freebayes_repository = self._get_repository_by_name_and_owner(
diff --git a/lib/tool_shed/test/functional/test_0070_invalid_tool.py b/lib/tool_shed/test/functional/test_0070_invalid_tool.py
index 9462bdb3b251..df577f7ea4d1 100644
--- a/lib/tool_shed/test/functional/test_0070_invalid_tool.py
+++ b/lib/tool_shed/test/functional/test_0070_invalid_tool.py
@@ -32,7 +32,7 @@ def test_0005_create_category_and_repository(self):
)
self.user_populator().setup_bismark_repo(repository)
invalid_revision = self.get_repository_first_revision(repository)
- self.display_manage_repository_page(repository, strings_displayed=["Invalid tools"])
+ self.display_manage_repository_page(repository, strings_displayed=[self.invalid_tools_labels])
valid_revision = self.get_repository_tip(repository)
tool_guid = f"{self.url.replace('http://', '').rstrip('/')}/repos/user1/bismark_0070/bismark_methylation_extractor/0.7.7.3"
tool_metadata_strings_displayed = [
diff --git a/lib/tool_shed/test/functional/test_0100_complex_repository_dependencies.py b/lib/tool_shed/test/functional/test_0100_complex_repository_dependencies.py
index 6ff5a4a431ed..fb7625860558 100644
--- a/lib/tool_shed/test/functional/test_0100_complex_repository_dependencies.py
+++ b/lib/tool_shed/test/functional/test_0100_complex_repository_dependencies.py
@@ -1,6 +1,7 @@
import logging
import os
+from ..base.api import skip_if_api_v2
from ..base.twilltestcase import (
common,
ShedTwillTestCase,
@@ -44,10 +45,11 @@ def test_0005_create_bwa_package_repository(self):
strings_displayed=[],
)
self.add_file_to_repository(repository, "bwa/complex/tool_dependencies.xml")
- # Visit the manage repository page for package_bwa_0_5_9_0100.
- self.display_manage_repository_page(
- repository, strings_displayed=["Tool dependencies", "will not be", "to this repository"]
- )
+ if not self.is_v2:
+ # Visit the manage repository page for package_bwa_0_5_9_0100.
+ self.display_manage_repository_page(
+ repository, strings_displayed=["Tool dependencies", "will not be", "to this repository"]
+ )
def test_0010_create_bwa_base_repository(self):
"""Create and populate bwa_base_0100."""
@@ -183,10 +185,12 @@ def test_0035_generate_complex_repository_dependency(self):
version="0.5.9",
)
self.check_repository_dependency(base_repository, depends_on_repository=tool_repository)
- self.display_manage_repository_page(
- base_repository, strings_displayed=["bwa", "0.5.9", "package", changeset_revision]
- )
+ if not self.is_v2:
+ self.display_manage_repository_page(
+ base_repository, strings_displayed=["bwa", "0.5.9", "package", changeset_revision]
+ )
+ @skip_if_api_v2
def test_0040_generate_tool_dependency(self):
"""Generate and upload a new tool_dependencies.xml file that specifies an arbitrary file on the filesystem, and verify that bwa_base depends on the new changeset revision."""
# The base_repository named bwa_base_repository_0100 is the dependent repository.
diff --git a/lib/tool_shed/test/functional/test_0120_simple_repository_dependency_multiple_owners.py b/lib/tool_shed/test/functional/test_0120_simple_repository_dependency_multiple_owners.py
index 71faea2f7758..71cd3322379f 100644
--- a/lib/tool_shed/test/functional/test_0120_simple_repository_dependency_multiple_owners.py
+++ b/lib/tool_shed/test/functional/test_0120_simple_repository_dependency_multiple_owners.py
@@ -65,16 +65,8 @@ def test_0010_verify_datatypes_repository(self):
the datatypes that are defined in datatypes_conf.xml.
"""
repository = self._get_repository_by_name_and_owner(datatypes_repository_name, common.test_user_2_name)
- strings_displayed = [
- "BlastXml",
- "BlastNucDb",
- "BlastProtDb",
- "application/xml",
- "text/html",
- "blastxml",
- "blastdbn",
- "blastdbp",
- ]
+ # v2 rightfully doesn't display anything about datatypes...
+ strings_displayed = ["Galaxy datatypes for the BLAST top hit"]
self.display_manage_repository_page(repository, strings_displayed=strings_displayed)
def test_0015_create_tool_repository(self):
@@ -108,7 +100,9 @@ def test_0020_verify_tool_repository(self):
"""
repository = self._get_repository_by_name_and_owner(tool_repository_name, common.test_user_1_name)
strings_displayed = ["blastxml_to_top_descr_0120", "BLAST top hit descriptions", "Make a table from BLAST XML"]
- strings_displayed.extend(["0.0.1", "Valid tools"])
+ strings_displayed.append("0.0.1")
+ if not self.is_v2:
+ strings_displayed.append("Valid tools")
self.display_manage_repository_page(repository, strings_displayed=strings_displayed)
def test_0025_create_repository_dependency(self):
diff --git a/lib/tool_shed/test/functional/test_0140_tool_help_images.py b/lib/tool_shed/test/functional/test_0140_tool_help_images.py
index bce376d06a6c..1a8247747700 100644
--- a/lib/tool_shed/test/functional/test_0140_tool_help_images.py
+++ b/lib/tool_shed/test/functional/test_0140_tool_help_images.py
@@ -1,5 +1,6 @@
import logging
+from ..base.api import skip_if_api_v2
from ..base.twilltestcase import (
common,
ShedTwillTestCase,
@@ -56,6 +57,7 @@ def test_0005_create_htseq_count_repository(self):
commit_message="Uploaded htseq_count.tar.",
)
+ @skip_if_api_v2
def test_0010_load_tool_page(self):
"""Load the tool page and check for the image.
diff --git a/lib/tool_shed/test/functional/test_0170_complex_prior_installation_required.py b/lib/tool_shed/test/functional/test_0170_complex_prior_installation_required.py
index 6b2b78107609..d0d1b3b1bad4 100644
--- a/lib/tool_shed/test/functional/test_0170_complex_prior_installation_required.py
+++ b/lib/tool_shed/test/functional/test_0170_complex_prior_installation_required.py
@@ -125,6 +125,7 @@ def test_0020_verify_generated_dependency(self):
)
changeset_revision = self.get_repository_tip(numpy_repository)
self.check_repository_dependency(matplotlib_repository, depends_on_repository=numpy_repository)
- self.display_manage_repository_page(
- matplotlib_repository, strings_displayed=["numpy", "1.7", "package", changeset_revision]
- )
+ if not self.is_v2:
+ self.display_manage_repository_page(
+ matplotlib_repository, strings_displayed=["numpy", "1.7", "package", changeset_revision]
+ )
diff --git a/lib/tool_shed/test/functional/test_0420_citable_urls_for_repositories.py b/lib/tool_shed/test/functional/test_0420_citable_urls_for_repositories.py
index 31d06c129fe2..0a46eec3fe7b 100644
--- a/lib/tool_shed/test/functional/test_0420_citable_urls_for_repositories.py
+++ b/lib/tool_shed/test/functional/test_0420_citable_urls_for_repositories.py
@@ -9,7 +9,7 @@
repository_name = "filtering_0420"
repository_description = "Galaxy filtering tool for test 0420"
-repository_long_description = "Long description of Galaxy filtering tool for test 0410"
+repository_long_description = "Long description of Galaxy filtering tool for test 0420"
first_changeset_hash = ""
@@ -88,9 +88,12 @@ def test_0015_load_user_view_page(self):
# Since twill does not load the contents of an iframe, we need to check that the iframe has been generated correctly,
# then directly load the url that the iframe should be loading and check for the expected strings.
# The iframe should point to /repository/browse_repositories?user_id=&operation=repositories_by_user
- strings_displayed = ["/repository/browse_repositories", encoded_user_id, "operation=repositories_by_user"]
- strings_displayed.append(encoded_user_id)
- strings_displayed_in_iframe = ["user1", "filtering_0420", "Galaxy filtering tool for test 0420"]
+ if self.is_v2:
+ strings_displayed = []
+ else:
+ strings_displayed = ["/repository/browse_repositories", encoded_user_id, "operation=repositories_by_user"]
+ strings_displayed.append(encoded_user_id)
+ strings_displayed_in_iframe = ["user1", "filtering_0420", repository_description]
self.load_citable_url(
username="user1",
repository_name=None,
@@ -115,11 +118,19 @@ def test_0020_load_repository_view_page(self):
# Since twill does not load the contents of an iframe, we need to check that the iframe has been generated correctly,
# then directly load the url that the iframe should be loading and check for the expected strings.
# The iframe should point to /repository/bview_repository?id=
- strings_displayed = ["/repository", "view_repository", "id=", encoded_repository_id]
- strings_displayed_in_iframe = ["user1", "filtering_0420", "Galaxy filtering tool for test 0420"]
+ if self.is_v2:
+ strings_displayed = []
+ else:
+ strings_displayed = ["/repository", "view_repository", "id=", encoded_repository_id]
+ strings_displayed_in_iframe = [
+ "user1",
+ "filtering_0420",
+ self._escape_page_content_if_needed(repository_long_description),
+ ]
strings_displayed_in_iframe.append(self.get_repository_tip(repository))
- strings_displayed_in_iframe.append("Link to this repository:")
- strings_displayed_in_iframe.append(f"{self.url}/view/user1/filtering_0420")
+ if not self.is_v2:
+ strings_displayed_in_iframe.append("Link to this repository:")
+ strings_displayed_in_iframe.append(f"{self.url}/view/user1/filtering_0420")
self.load_citable_url(
username="user1",
repository_name="filtering_0420",
@@ -145,15 +156,19 @@ def test_0025_load_view_page_for_previous_revision(self):
# Since twill does not load the contents of an iframe, we need to check that the iframe has been generated correctly,
# then directly load the url that the iframe should be loading and check for the expected strings.
# The iframe should point to /repository/view_repository?id=
- strings_displayed = ["/repository", "view_repository", f"id={encoded_repository_id}"]
+ if self.is_v2:
+ strings_displayed = []
+ else:
+ strings_displayed = ["/repository", "view_repository", f"id={encoded_repository_id}"]
strings_displayed_in_iframe = [
"user1",
"filtering_0420",
- "Galaxy filtering tool for test 0420",
+ self._escape_page_content_if_needed(repository_long_description),
first_changeset_hash,
]
- strings_displayed_in_iframe.append("Link to this repository revision:")
- strings_displayed_in_iframe.append(f"{self.url}/view/user1/filtering_0420/{first_changeset_hash}")
+ if not self.is_v2:
+ strings_displayed_in_iframe.append("Link to this repository revision:")
+ strings_displayed_in_iframe.append(f"{self.url}/view/user1/filtering_0420/{first_changeset_hash}")
strings_not_displayed_in_iframe = []
self.load_citable_url(
username="user1",
@@ -173,13 +188,16 @@ def test_0030_load_sharable_url_with_invalid_changeset_revision(self):
encoded_user_id = self.security.encode_id(test_user_1.id)
encoded_repository_id = repository.id
invalid_changeset_hash = "invalid"
- # Since twill does not load the contents of an iframe, we need to check that the iframe has been generated correctly,
- # then directly load the url that the iframe should be loading and check for the expected strings.
- # The iframe should point to /repository/view_repository?id=&status=error
- strings_displayed = ["/repository", "view_repository", f"id={encoded_repository_id}"]
- strings_displayed.extend(
- ["The+change+log", "does+not+include+revision", invalid_changeset_hash, "status=error"]
- )
+ if not self.is_v2:
+ # Since twill does not load the contents of an iframe, we need to check that the iframe has been generated correctly,
+ # then directly load the url that the iframe should be loading and check for the expected strings.
+ # The iframe should point to /repository/view_repository?id=&status=error
+ strings_displayed = ["/repository", "view_repository", f"id={encoded_repository_id}"]
+ strings_displayed.extend(
+ ["The+change+log", "does+not+include+revision", invalid_changeset_hash, "status=error"]
+ )
+ else:
+ strings_displayed = ["The change log does not include revision " + invalid_changeset_hash]
self.load_citable_url(
username="user1",
repository_name="filtering_0420",
@@ -200,12 +218,16 @@ def test_0035_load_sharable_url_with_invalid_repository_name(self):
# Since twill does not load the contents of an iframe, we need to check that the iframe has been generated correctly,
# then directly load the url that the iframe should be loading and check for the expected strings.
# The iframe should point to /repository/browse_repositories?user_id=&operation=repositories_by_user
- strings_displayed = ["/repository", "browse_repositories", "user1"]
- strings_displayed.extend(
- ["list+of+repositories+owned", "does+not+include+one+named", "%21%21invalid%21%21", "status=error"]
- )
- strings_displayed_in_iframe = ["user1", "filtering_0420"]
- strings_displayed_in_iframe.append("Repositories Owned by user1")
+ if not self.is_v2:
+ strings_displayed = ["/repository", "browse_repositories", "user1"]
+ strings_displayed.extend(
+ ["list+of+repositories+owned", "does+not+include+one+named", "%21%21invalid%21%21", "status=error"]
+ )
+ strings_displayed_in_iframe = ["user1", "filtering_0420"]
+ strings_displayed_in_iframe.append("Repositories Owned by user1")
+ else:
+ strings_displayed = ["Repository user1/!!invalid!! is not found"]
+ strings_displayed_in_iframe = []
self.load_citable_url(
username="user1",
repository_name="!!invalid!!",
@@ -222,7 +244,10 @@ def test_0040_load_sharable_url_with_invalid_owner(self):
We are at step 8.
Visit the following url and check for appropriate strings: /view/!!invalid!!
"""
- strings_displayed = ["The tool shed", self.url, "contains no repositories owned by", "!!invalid!!"]
+ if not self.is_v2:
+ strings_displayed = ["The tool shed", self.url, "contains no repositories owned by", "!!invalid!!"]
+ else:
+ strings_displayed = ["No repositories found"]
self.load_citable_url(
username="!!invalid!!",
repository_name=None,
diff --git a/lib/tool_shed/test/functional/test_0430_browse_utilities.py b/lib/tool_shed/test/functional/test_0430_browse_utilities.py
index 104d2e0e28b3..0202c5baf2ca 100644
--- a/lib/tool_shed/test/functional/test_0430_browse_utilities.py
+++ b/lib/tool_shed/test/functional/test_0430_browse_utilities.py
@@ -1,5 +1,6 @@
import logging
+from ..base.api import skip_if_api_v2
from ..base.twilltestcase import (
common,
ShedTwillTestCase,
@@ -85,6 +86,7 @@ def test_0020_create_tool_dependency_repository(self):
commit_message="Uploaded freebayes.tar.",
)
+ @skip_if_api_v2
def test_0030_browse_tools(self):
"""Load the page to browse tools.
@@ -96,6 +98,7 @@ def test_0030_browse_tools(self):
strings_displayed = ["EMBOSS", "antigenic1", "5.0.0", changeset_revision, "user1", "emboss_0430"]
self.browse_tools(strings_displayed=strings_displayed)
+ @skip_if_api_v2
def test_0040_browse_tool_dependencies(self):
"""Browse tool dependencies and look for the right versions of freebayes and samtools.
diff --git a/lib/tool_shed/test/functional/test_0460_upload_to_repository.py b/lib/tool_shed/test/functional/test_0460_upload_to_repository.py
index 5c8cb5ea9907..d1c04840ef8e 100644
--- a/lib/tool_shed/test/functional/test_0460_upload_to_repository.py
+++ b/lib/tool_shed/test/functional/test_0460_upload_to_repository.py
@@ -129,12 +129,13 @@ def test_0020_populate_complex_dependency_test_1_0460(self):
repository = self._get_repository_by_name_and_owner("complex_dependency_test_1_0460", common.test_user_1_name)
package_repository = self._get_repository_by_name_and_owner("package_bwa_0_5_9_0460", common.test_user_1_name)
self.add_file_to_repository(repository, "0460_files/tool_dependencies.xml")
- changeset_revision = self.get_repository_tip(package_repository)
- strings_displayed = ["package_bwa_0_5_9_0460", "bwa", "0.5.9", "package", changeset_revision]
- self.display_manage_repository_page(repository, strings_displayed=strings_displayed)
- self.display_repository_file_contents(
- repository, filename="tool_dependencies.xml", strings_displayed=[changeset_revision]
- )
+ if not self.is_v2:
+ changeset_revision = self.get_repository_tip(package_repository)
+ strings_displayed = ["package_bwa_0_5_9_0460", "bwa", "0.5.9", "package", changeset_revision]
+ self.display_manage_repository_page(repository, strings_displayed=strings_displayed)
+ self.display_repository_file_contents(
+ repository, filename="tool_dependencies.xml", strings_displayed=[changeset_revision]
+ )
def test_0025_populate_complex_dependency_test_2_0460(self):
"""Populate complex_dependency_test_2_0460.
@@ -149,12 +150,13 @@ def test_0025_populate_complex_dependency_test_2_0460(self):
"0460_files/tool_dependencies_in_root.tar",
commit_message="Uploaded complex repository dependency definition.",
)
- changeset_revision = self.get_repository_tip(package_repository)
- strings_displayed = ["package_bwa_0_5_9_0460", "bwa", "0.5.9", "package", changeset_revision]
- self.display_manage_repository_page(repository, strings_displayed=strings_displayed)
- self.display_repository_file_contents(
- repository, filename="tool_dependencies.xml", strings_displayed=[changeset_revision]
- )
+ if not self.is_v2:
+ changeset_revision = self.get_repository_tip(package_repository)
+ strings_displayed = ["package_bwa_0_5_9_0460", "bwa", "0.5.9", "package", changeset_revision]
+ self.display_manage_repository_page(repository, strings_displayed=strings_displayed)
+ self.display_repository_file_contents(
+ repository, filename="tool_dependencies.xml", strings_displayed=[changeset_revision]
+ )
def test_0030_populate_complex_dependency_test_3_0460(self):
"""Populate complex_dependency_test_3_0460.
@@ -170,11 +172,15 @@ def test_0030_populate_complex_dependency_test_3_0460(self):
commit_message="Uploaded complex repository dependency definition.",
)
changeset_revision = self.get_repository_tip(package_repository)
- strings_displayed = ["package_bwa_0_5_9_0460", "bwa", "0.5.9", "package", changeset_revision]
- self.display_manage_repository_page(repository, strings_displayed=strings_displayed)
- self.display_repository_file_contents(
- repository, filename="tool_dependencies.xml", filepath="subfolder", strings_displayed=[changeset_revision]
- )
+ if not self.is_v2:
+ strings_displayed = ["package_bwa_0_5_9_0460", "bwa", "0.5.9", "package", changeset_revision]
+ self.display_manage_repository_page(repository, strings_displayed=strings_displayed)
+ self.display_repository_file_contents(
+ repository,
+ filename="tool_dependencies.xml",
+ filepath="subfolder",
+ strings_displayed=[changeset_revision],
+ )
def test_0035_create_repositories_for_url_upload(self):
"""Create and populate hg_tool_dependency_0460 and hg_subfolder_tool_dependency_0460.
@@ -244,11 +250,12 @@ def test_0055_populate_repository_dependency_test_1_0460(self):
package_repository = self._get_repository_by_name_and_owner(bwa_repository_name, common.test_user_1_name)
self.add_file_to_repository(repository, "0460_files/repository_dependencies.xml")
changeset_revision = self.get_repository_tip(package_repository)
- strings_displayed = [bwa_repository_name, "user1", changeset_revision]
- self.display_manage_repository_page(repository, strings_displayed=strings_displayed)
- self.display_repository_file_contents(
- repository, filename="repository_dependencies.xml", strings_displayed=[changeset_revision]
- )
+ if not self.is_v2:
+ strings_displayed = [bwa_repository_name, "user1", changeset_revision]
+ self.display_manage_repository_page(repository, strings_displayed=strings_displayed)
+ self.display_repository_file_contents(
+ repository, filename="repository_dependencies.xml", strings_displayed=[changeset_revision]
+ )
def test_0060_populate_repository_dependency_test_2_0460(self):
"""Populate repository_dependency_test_2_0460.
@@ -265,11 +272,12 @@ def test_0060_populate_repository_dependency_test_2_0460(self):
commit_message="Uploaded complex repository dependency definition.",
)
changeset_revision = self.get_repository_tip(package_repository)
- strings_displayed = [bwa_repository_name, "user1", changeset_revision]
- self.display_manage_repository_page(repository, strings_displayed=strings_displayed)
- self.display_repository_file_contents(
- repository, filename="repository_dependencies.xml", strings_displayed=[changeset_revision]
- )
+ if not self.is_v2:
+ strings_displayed = [bwa_repository_name, "user1", changeset_revision]
+ self.display_manage_repository_page(repository, strings_displayed=strings_displayed)
+ self.display_repository_file_contents(
+ repository, filename="repository_dependencies.xml", strings_displayed=[changeset_revision]
+ )
def test_0065_populate_repository_dependency_test_3_0460(self):
"""Populate repository_dependency_test_3_0460.
@@ -287,14 +295,15 @@ def test_0065_populate_repository_dependency_test_3_0460(self):
commit_message="Uploaded complex repository dependency definition.",
)
changeset_revision = self.get_repository_tip(package_repository)
- strings_displayed = [bwa_repository_name, "user1", changeset_revision]
- self.display_manage_repository_page(repository, strings_displayed=strings_displayed)
- self.display_repository_file_contents(
- repository,
- filename="repository_dependencies.xml",
- filepath="subfolder",
- strings_displayed=[changeset_revision],
- )
+ if not self.is_v2:
+ strings_displayed = [bwa_repository_name, "user1", changeset_revision]
+ self.display_manage_repository_page(repository, strings_displayed=strings_displayed)
+ self.display_repository_file_contents(
+ repository,
+ filename="repository_dependencies.xml",
+ filepath="subfolder",
+ strings_displayed=[changeset_revision],
+ )
def test_0070_create_repositories_for_url_upload(self):
"""Create and populate hg_repository_dependency_0460 and hg_subfolder_repository_dependency_0460.
diff --git a/lib/tool_shed/test/functional/test_0530_repository_admin_feature.py b/lib/tool_shed/test/functional/test_0530_repository_admin_feature.py
index 19294287a283..348f70281e88 100644
--- a/lib/tool_shed/test/functional/test_0530_repository_admin_feature.py
+++ b/lib/tool_shed/test/functional/test_0530_repository_admin_feature.py
@@ -1,5 +1,6 @@
import logging
+from ..base.api import skip_if_api_v2
from ..base.twilltestcase import (
common,
ShedTwillTestCase,
@@ -98,6 +99,7 @@ def test_0020_rename_repository(self):
repository = self._get_repository_by_name_and_owner("renamed_filtering_0530", common.test_user_1_name)
assert repository.name == "renamed_filtering_0530", "Repository was not renamed to renamed_filtering_0530."
+ @skip_if_api_v2
def test_0030_verify_access_denied(self):
"""Make sure a non-admin user can't modify the repository.
diff --git a/lib/tool_shed/test/functional/test_0550_metadata_updated_dependencies.py b/lib/tool_shed/test/functional/test_0550_metadata_updated_dependencies.py
index f622b05ac2ab..8c5b08cec3f9 100644
--- a/lib/tool_shed/test/functional/test_0550_metadata_updated_dependencies.py
+++ b/lib/tool_shed/test/functional/test_0550_metadata_updated_dependencies.py
@@ -71,10 +71,11 @@ def test_0005_freebayes_repository(self):
freebayes,
"0550_files/package_freebayes_1_0550.tgz",
)
- # Visit the manage repository page for package_freebayes_0_5_9_0100.
- self.display_manage_repository_page(
- freebayes, strings_displayed=["Tool dependencies", "will not be", "to this repository"]
- )
+ if not self.is_v2:
+ # Visit the manage repository page for package_freebayes_0_5_9_0100.
+ self.display_manage_repository_page(
+ freebayes, strings_displayed=["Tool dependencies", "will not be", "to this repository"]
+ )
def test_0010_create_samtools_repository(self):
"""Create and populate the package_samtools_0550 repository."""
@@ -118,7 +119,8 @@ def test_0020_check_repository_dependency(self):
samtools = self._get_repository_by_name_and_owner(repositories["samtools"]["name"], common.test_user_1_name)
filtering = self._get_repository_by_name_and_owner(repositories["filtering"]["name"], common.test_user_1_name)
strings_displayed = [freebayes.id, samtools.id]
- self.display_manage_repository_page(filtering, strings_displayed=strings_displayed)
+ if not self.is_v2:
+ self.display_manage_repository_page(filtering, strings_displayed=strings_displayed)
def test_0025_update_dependent_repositories(self):
"""
diff --git a/lib/tool_shed/test/functional/test_1010_install_repository_with_tool_dependencies.py b/lib/tool_shed/test/functional/test_1010_install_repository_with_tool_dependencies.py
index 2df6669aee2f..976d2c354ecc 100644
--- a/lib/tool_shed/test/functional/test_1010_install_repository_with_tool_dependencies.py
+++ b/lib/tool_shed/test/functional/test_1010_install_repository_with_tool_dependencies.py
@@ -43,10 +43,11 @@ def test_0010_browse_tool_shed(self):
self.browse_tool_shed(url=self.url, strings_displayed=[category_name])
category = self.populator.get_category_with_name(category_name)
self.browse_category(category, strings_displayed=[repository_name])
- strings_displayed = [repository_name, "Valid tools", "Tool dependencies"]
- self.preview_repository_in_tool_shed(
- repository_name, common.test_user_1_name, strings_displayed=strings_displayed
- )
+ if not self.is_v2:
+ strings_displayed = [repository_name, "Valid tools", "Tool dependencies"]
+ self.preview_repository_in_tool_shed(
+ repository_name, common.test_user_1_name, strings_displayed=strings_displayed
+ )
def test_0015_install_freebayes_repository(self):
"""Install the freebayes repository without installing tool dependencies."""
diff --git a/lib/tool_shed/test/functional/test_1020_install_repository_with_repository_dependencies.py b/lib/tool_shed/test/functional/test_1020_install_repository_with_repository_dependencies.py
index fbcfbb28d3be..b965011e7ca0 100644
--- a/lib/tool_shed/test/functional/test_1020_install_repository_with_repository_dependencies.py
+++ b/lib/tool_shed/test/functional/test_1020_install_repository_with_repository_dependencies.py
@@ -73,9 +73,12 @@ def test_0010_browse_tool_shed(self):
self.browse_tool_shed(url=self.url, strings_displayed=["Test 0020 Basic Repository Dependencies"])
category = self.populator.get_category_with_name("Test 0020 Basic Repository Dependencies")
self.browse_category(category, strings_displayed=[emboss_repository_name])
- self.preview_repository_in_tool_shed(
- emboss_repository_name, common.test_user_1_name, strings_displayed=[emboss_repository_name, "Valid tools"]
- )
+ if not self.is_v2:
+ self.preview_repository_in_tool_shed(
+ emboss_repository_name,
+ common.test_user_1_name,
+ strings_displayed=[emboss_repository_name, "Valid tools"],
+ )
def test_0015_install_emboss_repository(self):
"""Install the emboss repository without installing tool dependencies."""
diff --git a/lib/tool_shed/test/functional/test_1030_install_repository_with_dependency_revisions.py b/lib/tool_shed/test/functional/test_1030_install_repository_with_dependency_revisions.py
index 17cebc758712..3cc894c038cb 100644
--- a/lib/tool_shed/test/functional/test_1030_install_repository_with_dependency_revisions.py
+++ b/lib/tool_shed/test/functional/test_1030_install_repository_with_dependency_revisions.py
@@ -140,9 +140,12 @@ def test_0010_browse_tool_shed(self):
self.browse_tool_shed(url=self.url, strings_displayed=["Test 0030 Repository Dependency Revisions"])
category = self.populator.get_category_with_name("Test 0030 Repository Dependency Revisions")
self.browse_category(category, strings_displayed=[emboss_repository_name])
- self.preview_repository_in_tool_shed(
- emboss_repository_name, common.test_user_1_name, strings_displayed=[emboss_repository_name, "Valid tools"]
- )
+ if not self.is_v2:
+ self.preview_repository_in_tool_shed(
+ emboss_repository_name,
+ common.test_user_1_name,
+ strings_displayed=[emboss_repository_name, "Valid tools"],
+ )
def test_0015_install_emboss_repository(self):
"""Install the emboss repository without installing tool dependencies."""
diff --git a/lib/tool_shed/test/functional/test_1050_circular_dependencies_4_levels.py b/lib/tool_shed/test/functional/test_1050_circular_dependencies_4_levels.py
index 48e7039032a3..e57633296faa 100644
--- a/lib/tool_shed/test/functional/test_1050_circular_dependencies_4_levels.py
+++ b/lib/tool_shed/test/functional/test_1050_circular_dependencies_4_levels.py
@@ -269,7 +269,8 @@ def test_0045_verify_repository_dependencies(self):
strings_displayed = [
f"{freebayes_repository.name} depends on {', '.join(repo.name for repo in freebayes_dependencies)}."
]
- self.display_manage_repository_page(freebayes_repository, strings_displayed=strings_displayed)
+ if not self.is_v2:
+ self.display_manage_repository_page(freebayes_repository, strings_displayed=strings_displayed)
def test_0050_verify_tool_dependencies(self):
"""Check that freebayes and emboss display tool dependencies."""
@@ -277,13 +278,14 @@ def test_0050_verify_tool_dependencies(self):
freebayes_repository_name, common.test_user_1_name
)
emboss_repository = self._get_repository_by_name_and_owner(emboss_repository_name, common.test_user_1_name)
- self.display_manage_repository_page(
- freebayes_repository,
- strings_displayed=["freebayes", "0.9.4_9696d0ce8a9", "samtools", "0.1.18", "Tool dependencies"],
- )
- self.display_manage_repository_page(
- emboss_repository, strings_displayed=["Tool dependencies", "emboss", "5.0.0", "package"]
- )
+ if not self.is_v2:
+ self.display_manage_repository_page(
+ freebayes_repository,
+ strings_displayed=["freebayes", "0.9.4_9696d0ce8a9", "samtools", "0.1.18", "Tool dependencies"],
+ )
+ self.display_manage_repository_page(
+ emboss_repository, strings_displayed=["Tool dependencies", "emboss", "5.0.0", "package"]
+ )
def test_0055_install_column_repository(self):
"""Install column_maker with repository dependencies."""
diff --git a/lib/tool_shed/test/functional/test_1140_simple_repository_dependency_multiple_owners.py b/lib/tool_shed/test/functional/test_1140_simple_repository_dependency_multiple_owners.py
index 557f20fa8ed6..12067ac1bce8 100644
--- a/lib/tool_shed/test/functional/test_1140_simple_repository_dependency_multiple_owners.py
+++ b/lib/tool_shed/test/functional/test_1140_simple_repository_dependency_multiple_owners.py
@@ -84,7 +84,8 @@ def test_0010_verify_datatypes_repository(self):
"blastdbn",
"blastdbp",
]
- self.display_manage_repository_page(repository, strings_displayed=strings_displayed)
+ if not self.is_v2:
+ self.display_manage_repository_page(repository, strings_displayed=strings_displayed)
def test_0015_create_tool_repository(self):
"""Create and populate the blastxml_to_top_descr_0120 repository
@@ -126,7 +127,7 @@ def test_0020_verify_tool_repository(self):
"""
repository = self._get_repository_by_name_and_owner(tool_repository_name, common.test_user_1_name)
strings_displayed = ["blastxml_to_top_descr_0120", "BLAST top hit descriptions", "Make a table from BLAST XML"]
- strings_displayed.extend(["0.0.1", "Valid tools"])
+ strings_displayed.extend(["0.0.1"])
self.display_manage_repository_page(repository, strings_displayed=strings_displayed)
def test_0025_create_repository_dependency(self):
diff --git a/lib/tool_shed/test/functional/test_1160_tool_help_images.py b/lib/tool_shed/test/functional/test_1160_tool_help_images.py
index dd46f0dbae92..add92d62dc55 100644
--- a/lib/tool_shed/test/functional/test_1160_tool_help_images.py
+++ b/lib/tool_shed/test/functional/test_1160_tool_help_images.py
@@ -69,6 +69,8 @@ def test_0010_load_tool_page(self):
# should be the tool that contains a link to the image.
repository_metadata = self._db_repository(repository).metadata_revisions[0].metadata
tool_path = repository_metadata["tools"][0]["tool_config"]
- self.load_display_tool_page(
- repository, tool_path, changeset_revision, strings_displayed=[image_path], strings_not_displayed=[]
- )
+ # V2 is not going to have this page right? So... do we need this test at all or that route? Likely not?
+ if self._browser.is_twill and not self.is_v2:
+ self.load_display_tool_page(
+ repository, tool_path, changeset_revision, strings_displayed=[image_path], strings_not_displayed=[]
+ )
diff --git a/lib/tool_shed/test/functional/test_1190_complex_prior_installation_required.py b/lib/tool_shed/test/functional/test_1190_complex_prior_installation_required.py
index 57714179cd0e..b49ae8998ca2 100644
--- a/lib/tool_shed/test/functional/test_1190_complex_prior_installation_required.py
+++ b/lib/tool_shed/test/functional/test_1190_complex_prior_installation_required.py
@@ -136,9 +136,10 @@ def test_0020_verify_generated_dependency(self):
)
changeset_revision = self.get_repository_tip(numpy_repository)
self.check_repository_dependency(matplotlib_repository, depends_on_repository=numpy_repository)
- self.display_manage_repository_page(
- matplotlib_repository, strings_displayed=["numpy", "1.7", "package", changeset_revision]
- )
+ if not self.is_v2:
+ self.display_manage_repository_page(
+ matplotlib_repository, strings_displayed=["numpy", "1.7", "package", changeset_revision]
+ )
def test_0025_install_matplotlib_repository(self):
"""Install the package_matplotlib_1_2_0170 repository.
diff --git a/lib/tool_shed/test/functional/test_frontend_login.py b/lib/tool_shed/test/functional/test_frontend_login.py
new file mode 100644
index 000000000000..1e752c37e934
--- /dev/null
+++ b/lib/tool_shed/test/functional/test_frontend_login.py
@@ -0,0 +1,76 @@
+from playwright.sync_api import (
+ expect,
+ Page,
+)
+
+from galaxy_test.base.api_util import random_name
+from ..base.api import skip_if_api_v1
+from ..base.playwrightbrowser import (
+ Locators,
+ PlaywrightShedBrowser,
+)
+from ..base.twilltestcase import ShedTwillTestCase
+
+
+class PlaywrightTestCase(ShedTwillTestCase):
+ @property
+ def _playwright_browser(self) -> PlaywrightShedBrowser:
+ browser = self._browser
+ assert isinstance(browser, PlaywrightShedBrowser)
+ return browser
+
+ @property
+ def _page(self) -> Page:
+ return self._playwright_browser._page
+
+
+TEST_PASSWORD = "testpass"
+
+
+class TestFrontendLogin(PlaywrightTestCase):
+ @skip_if_api_v1
+ def test_register(self):
+ self.visit_url("/")
+ page = self._page
+ expect(page.locator(Locators.toolbar_login)).to_be_visible()
+ page.click(Locators.toolbar_login)
+ expect(page.locator(Locators.login_submit_button)).to_be_visible()
+ expect(page.locator(Locators.register_link)).to_be_visible()
+ page.click(Locators.register_link)
+ user = random_name(prefix="shduser")
+ self._submit_register_form(
+ f"{user}@galaxyproject.org",
+ TEST_PASSWORD,
+ user,
+ )
+ expect(page.locator(Locators.login_submit_button)).to_be_visible()
+
+ @skip_if_api_v1
+ def test_create(self):
+ user = random_name(prefix="shduser")
+ self.create(
+ email=f"{user}@galaxyproject.org",
+ password=TEST_PASSWORD,
+ username=user,
+ )
+
+ @skip_if_api_v1
+ def test_logout(self):
+ self._create_and_login()
+ self._playwright_browser.expect_logged_in()
+ self._playwright_browser.logout_if_logged_in()
+ self._playwright_browser.expect_not_logged_in()
+
+ @skip_if_api_v1
+ def test_change_password(self):
+ self._create_and_login()
+
+ def _create_and_login(self):
+ user = random_name(prefix="shduser")
+ email = f"{user}@galaxyproject.org"
+ self.create(
+ email=email,
+ password=TEST_PASSWORD,
+ username=user,
+ )
+ self.login(email, TEST_PASSWORD, username=user, redirect=None)
diff --git a/lib/tool_shed/test/functional/test_shed_graphql.py b/lib/tool_shed/test/functional/test_shed_graphql.py
new file mode 100644
index 000000000000..c427732872d8
--- /dev/null
+++ b/lib/tool_shed/test/functional/test_shed_graphql.py
@@ -0,0 +1,21 @@
+from galaxy_test.base.api_asserts import assert_status_code_is_ok
+from ..base.api import (
+ ShedApiTestCase,
+ skip_if_api_v1,
+)
+
+
+class TestShedGraphqlApi(ShedApiTestCase):
+ @skip_if_api_v1
+ def test_graphql_query(self):
+ populator = self.populator
+ category = populator.new_category(prefix="testcreate")
+ json = {"query": r"query { categories { name } }"}
+ response = self.api_interactor.post("graphql/", json=json)
+ assert_status_code_is_ok(response)
+ result = response.json()
+ assert "data" in result
+ data = result["data"]
+ assert "categories" in data
+ categories = data["categories"]
+ assert category.name in [c["name"] for c in categories]
diff --git a/lib/tool_shed/test/functional/test_shed_repositories.py b/lib/tool_shed/test/functional/test_shed_repositories.py
index 2fd4f551a08e..90e9b8134059 100644
--- a/lib/tool_shed/test/functional/test_shed_repositories.py
+++ b/lib/tool_shed/test/functional/test_shed_repositories.py
@@ -61,6 +61,14 @@ def test_metadata_simple(self):
assert only_revision.downloadable
assert not only_revision.malicious
+ def test_metadata_invalid_tools(self):
+ populator = self.populator
+ repository = populator.setup_bismark_repo()
+ repository_metadata = populator.get_metadata(repository)
+ assert repository_metadata
+ for _, value in repository_metadata.__root__.items():
+ assert value.invalid_tools
+
def test_index_simple(self):
# Logic and typing is pretty different if given a tool id to search for - this should
# be tested or dropped in v2.
diff --git a/lib/tool_shed/util/metadata_util.py b/lib/tool_shed/util/metadata_util.py
index 47d82928f0b6..c41b62b7a7c0 100644
--- a/lib/tool_shed/util/metadata_util.py
+++ b/lib/tool_shed/util/metadata_util.py
@@ -45,6 +45,7 @@ def get_all_dependencies(app, metadata_entry, processed_dependency_links=None):
dependency_dict["repository"] = repository.to_dict(value_mapper=value_mapper)
if dependency_metadata.includes_tools:
dependency_dict["tools"] = dependency_metadata.metadata["tools"]
+ dependency_dict["invalid_tools"] = dependency_metadata.metadata.get("invalid_tools", [])
dependency_dict["repository_dependencies"] = []
if dependency_dict["includes_tool_dependencies"]:
dependency_dict["tool_dependencies"] = repository.get_tool_dependencies(
diff --git a/lib/tool_shed/webapp/api2/__init__.py b/lib/tool_shed/webapp/api2/__init__.py
index 7f270db14d4a..6961c8407b93 100644
--- a/lib/tool_shed/webapp/api2/__init__.py
+++ b/lib/tool_shed/webapp/api2/__init__.py
@@ -1,3 +1,4 @@
+import logging
from json import JSONDecodeError
from typing import (
AsyncGenerator,
@@ -29,7 +30,9 @@
from galaxy.managers.session import GalaxySessionManager
from galaxy.managers.users import UserManager
from galaxy.security.idencoding import IdEncodingHelper
+from galaxy.util import unicodify
from galaxy.web.framework.decorators import require_admin_message
+from galaxy.webapps.base.webapp import create_new_session
from galaxy.webapps.galaxy.api import (
depends as framework_depends,
FrameworkRouter,
@@ -49,6 +52,8 @@
User,
)
+log = logging.getLogger(__name__)
+
def get_app() -> ToolShedApp:
if tool_shed_app_mod.app is None:
@@ -67,10 +72,11 @@ async def get_app_with_request_session() -> AsyncGenerator[ToolShedApp, None]:
DependsOnApp = cast(ToolShedApp, Depends(get_app_with_request_session))
+AUTH_COOKIE_NAME = "galaxycommunitysession"
api_key_query = APIKeyQuery(name="key", auto_error=False)
api_key_header = APIKeyHeader(name="x-api-key", auto_error=False)
-api_key_cookie = APIKeyCookie(name="galaxycommunitysession", auto_error=False)
+api_key_cookie = APIKeyCookie(name=AUTH_COOKIE_NAME, auto_error=False)
def depends(dep_type: Type[T]) -> T:
@@ -218,7 +224,7 @@ async def get_body(request: Request):
DownloadableQueryParam: bool = Query(
default=True,
title="downloadable_only",
- description="Include only downable repositories.",
+ description="Include only downloadable repositories.",
)
CommitMessage: str = Query(
@@ -271,3 +277,79 @@ async def get_body(request: Request):
CategoryRepositoriesSortKeyQueryParam: str = Query("name", title="Sort Key")
CategoryRepositoriesSortOrderQueryParam: str = Query("asc", title="Sort Order")
CategoryRepositoriesPageQueryParam: Optional[int] = Query(None, title="Page")
+
+
+def ensure_valid_session(trans: SessionRequestContext) -> None:
+ """
+ Ensure that a valid Galaxy session exists and is available as
+ trans.session (part of initialization)
+ """
+ app = trans.app
+ mapping = app.model
+ session_manager = GalaxySessionManager(mapping)
+ sa_session = app.model.context
+ request = trans.request
+ # Try to load an existing session
+ secure_id = request.get_cookie(AUTH_COOKIE_NAME)
+ galaxy_session = None
+ prev_galaxy_session = None
+ user_for_new_session = None
+ invalidate_existing_session = False
+ # Track whether the session has changed so we can avoid calling flush
+ # in the most common case (session exists and is valid).
+ galaxy_session_requires_flush = False
+ if secure_id:
+ session_key: Optional[str] = app.security.decode_guid(secure_id)
+ if session_key:
+ # We do NOT catch exceptions here, if the database is down the request should fail,
+ # and we should not generate a new session.
+ galaxy_session = session_manager.get_session_from_session_key(session_key=session_key)
+ if not galaxy_session:
+ session_key = None
+
+ if galaxy_session is not None and galaxy_session.user is not None and galaxy_session.user.deleted:
+ invalidate_existing_session = True
+ log.warning(f"User '{galaxy_session.user.email}' is marked deleted, invalidating session")
+ # Do we need to invalidate the session for some reason?
+ if invalidate_existing_session:
+ assert galaxy_session
+ prev_galaxy_session = galaxy_session
+ prev_galaxy_session.is_valid = False
+ galaxy_session = None
+ # No relevant cookies, or couldn't find, or invalid, so create a new session
+ if galaxy_session is None:
+ galaxy_session = create_new_session(trans, prev_galaxy_session, user_for_new_session)
+ galaxy_session_requires_flush = True
+ trans.set_galaxy_session(galaxy_session)
+ set_auth_cookie(trans, galaxy_session)
+ else:
+ trans.set_galaxy_session(galaxy_session)
+ # Do we need to flush the session?
+ if galaxy_session_requires_flush:
+ sa_session.add(galaxy_session)
+ # FIXME: If prev_session is a proper relation this would not
+ # be needed.
+ if prev_galaxy_session:
+ sa_session.add(prev_galaxy_session)
+ sa_session.flush()
+
+
+def set_auth_cookie(trans: SessionRequestContext, session):
+ cookie_name = AUTH_COOKIE_NAME
+ set_cookie(trans, trans.app.security.encode_guid(session.session_key), cookie_name)
+
+
+def set_cookie(trans: SessionRequestContext, value: str, key, path="/", age=90) -> None:
+ """Convenience method for setting a session cookie"""
+ # In wsgi we were setting both a max_age and and expires, but
+ # all browsers support max_age now.
+ domain: Optional[str] = trans.app.config.cookie_domain
+ trans.response.set_cookie(
+ key,
+ unicodify(value),
+ path=path,
+ max_age=3600 * 24 * age, # 90 days
+ httponly=True,
+ secure=trans.request.is_secure,
+ domain=domain,
+ )
diff --git a/lib/tool_shed/webapp/api2/repositories.py b/lib/tool_shed/webapp/api2/repositories.py
index 9cbf95c91913..776e4064a7ab 100644
--- a/lib/tool_shed/webapp/api2/repositories.py
+++ b/lib/tool_shed/webapp/api2/repositories.py
@@ -190,6 +190,21 @@ def metadata(
return as_dict
# return _hack_fastapi_4428(as_dict)
+ @router.get(
+ "/api_internal/repositories/{encoded_repository_id}/metadata",
+ description="Get information about repository metadata",
+ operation_id="repositories__internal_metadata",
+ response_model=RepositoryMetadata,
+ )
+ def metadata_internal(
+ self,
+ encoded_repository_id: str = RepositoryIdPathParam,
+ downloadable_only: bool = DownloadableQueryParam,
+ ) -> dict:
+ recursive = True
+ as_dict = get_repository_metadata_dict(self.app, encoded_repository_id, recursive, downloadable_only)
+ return _hack_fastapi_4428(as_dict)
+
@router.get(
"/api/repositories/get_ordered_installable_revisions",
description="Get an ordered list of the repository changeset revisions that are installable",
diff --git a/lib/tool_shed/webapp/api2/users.py b/lib/tool_shed/webapp/api2/users.py
index 3e57735b6718..8873d8b9800f 100644
--- a/lib/tool_shed/webapp/api2/users.py
+++ b/lib/tool_shed/webapp/api2/users.py
@@ -1,3 +1,4 @@
+import logging
from typing import (
List,
Optional,
@@ -9,6 +10,10 @@
status,
)
from pydantic import BaseModel
+from sqlalchemy import (
+ and_,
+ true,
+)
import tool_shed.util.shed_util_common as suc
from galaxy.exceptions import (
@@ -17,12 +22,16 @@
RequestParameterInvalidException,
)
from galaxy.managers.api_keys import ApiKeyManager
+from galaxy.managers.users import UserManager
+from galaxy.webapps.base.webapp import create_new_session
from tool_shed.context import SessionRequestContext
from tool_shed.managers.users import (
api_create_user,
get_api_user,
index,
)
+from tool_shed.structured_app import ToolShedApp
+from tool_shed.webapp.model import User as SaUser
from tool_shed_client.schema import (
CreateUserRequest,
User,
@@ -30,15 +39,64 @@
from . import (
depends,
DependsOnTrans,
+ ensure_valid_session,
Router,
+ set_auth_cookie,
UserIdPathParam,
)
router = Router(tags=["users"])
+log = logging.getLogger(__name__)
+
+
+class UiRegisterRequest(BaseModel):
+ email: str
+ username: str
+ password: str
+ bear_field: str
+
+
+class HasCsrfToken(BaseModel):
+ session_csrf_token: str
+
+
+class UiLoginRequest(HasCsrfToken):
+ login: str
+ password: str
+
+
+class UiLogoutRequest(HasCsrfToken):
+ logout_all: bool = False
+
+
+class UiLoginResponse(BaseModel):
+ pass
+
+
+class UiLogoutResponse(BaseModel):
+ pass
+
+
+class UiRegisterResponse(BaseModel):
+ email: str
+ activation_sent: bool = False
+ activation_error: bool = False
+ contact_email: Optional[str] = None
+
+
+class UiChangePasswordRequest(BaseModel):
+ current: str
+ password: str
+
+
+INVALID_LOGIN_OR_PASSWORD = "Invalid login or password"
+
@router.cbv
class FastAPIUsers:
+ app: ToolShedApp = depends(ToolShedApp)
+ user_manager: UserManager = depends(UserManager)
api_key_manager: ApiKeyManager = depends(ApiKeyManager)
@router.get(
@@ -66,7 +124,9 @@ def create(self, trans: SessionRequestContext = DependsOnTrans, request: CreateU
)
def current(self, trans: SessionRequestContext = DependsOnTrans) -> User:
user = trans.user
- assert user
+ if not user:
+ raise ObjectNotFound()
+
return get_api_user(trans.app, user)
@router.get(
@@ -128,3 +188,153 @@ def _get_user(self, trans: SessionRequestContext, encoded_user_id: str):
if not (trans.user_is_admin or trans.user == user):
raise InsufficientPermissionsException()
return user
+
+ @router.post(
+ "/api_internal/register",
+ description="register a user",
+ operation_id="users__internal_register",
+ )
+ def register(
+ self, trans: SessionRequestContext = DependsOnTrans, request: UiRegisterRequest = Body(...)
+ ) -> UiRegisterResponse:
+ honeypot_field = request.bear_field
+ if honeypot_field != "":
+ message = "You've been flagged as a possible bot. If you are not, please try registering again and fill the form out carefully."
+ raise RequestParameterInvalidException(message)
+
+ username = request.username
+ if username == "repos":
+ raise RequestParameterInvalidException("Cannot create a user with the username 'repos'")
+ self.user_manager.create(email=request.email, username=username, password=request.password)
+ if self.app.config.user_activation_on:
+ is_activation_sent = self.user_manager.send_activation_email(trans, request.email, username)
+ if is_activation_sent:
+ return UiRegisterResponse(email=request.email, activation_sent=True)
+ else:
+ return UiRegisterResponse(
+ email=request.email,
+ activation_sent=False,
+ activation_error=True,
+ contact_email=self.app.config.error_email_to,
+ )
+ else:
+ return UiRegisterResponse(email=request.email)
+
+ @router.put(
+ "/api_internal/change_password",
+ description="reset a user",
+ operation_id="users__internal_change_password",
+ status_code=status.HTTP_204_NO_CONTENT,
+ )
+ def change_password(
+ self, trans: SessionRequestContext = DependsOnTrans, request: UiChangePasswordRequest = Body(...)
+ ):
+ password = request.password
+ current = request.current
+ if trans.user is None:
+ raise InsufficientPermissionsException("Must be logged into use this functionality")
+ user_id = trans.user.id
+ token = None
+ user, message = self.user_manager.change_password(
+ trans, password=password, current=current, token=token, confirm=password, id=user_id
+ )
+ if not user:
+ raise RequestParameterInvalidException(message)
+ return Response(status_code=status.HTTP_204_NO_CONTENT)
+
+ @router.put(
+ "/api_internal/login",
+ description="login to web UI",
+ operation_id="users__internal_login",
+ )
+ def internal_login(
+ self, trans: SessionRequestContext = DependsOnTrans, request: UiLoginRequest = Body(...)
+ ) -> UiLoginResponse:
+ log.info(f"top of internal_login {trans.session_csrf_token}")
+ ensure_csrf_token(trans, request)
+ login = request.login
+ password = request.password
+ user = self.user_manager.get_user_by_identity(login)
+ if user is None:
+ raise InsufficientPermissionsException(INVALID_LOGIN_OR_PASSWORD)
+ elif user.deleted:
+ message = (
+ "This account has been marked deleted, contact your local Galaxy administrator to restore the account."
+ )
+ if trans.app.config.error_email_to is not None:
+ message += f" Contact: {trans.app.config.error_email_to}."
+ raise InsufficientPermissionsException(message)
+ elif not trans.app.auth_manager.check_password(user, password, trans.request):
+ raise InsufficientPermissionsException(INVALID_LOGIN_OR_PASSWORD)
+ else:
+ handle_user_login(trans, user)
+ return UiLoginResponse()
+
+ @router.put(
+ "/api_internal/logout",
+ description="logout of web UI",
+ operation_id="users__internal_logout",
+ )
+ def internal_logout(
+ self, trans: SessionRequestContext = DependsOnTrans, request: UiLogoutRequest = Body(...)
+ ) -> UiLogoutResponse:
+ ensure_csrf_token(trans, request)
+ handle_user_logout(trans, logout_all=request.logout_all)
+ return UiLogoutResponse()
+
+
+def ensure_csrf_token(trans: SessionRequestContext, request: HasCsrfToken):
+ session_csrf_token = request.session_csrf_token
+ if not trans.session_csrf_token:
+ ensure_valid_session(trans)
+ message = None
+ if not session_csrf_token:
+ message = "No session token set, denying request."
+ elif session_csrf_token != trans.session_csrf_token:
+ log.info(f"{session_csrf_token} != {trans.session_csrf_token}")
+ message = "Wrong session token found, denying request."
+ if message:
+ raise InsufficientPermissionsException(message)
+
+
+def handle_user_login(trans: SessionRequestContext, user: SaUser) -> None:
+ trans.app.security_agent.create_user_role(user, trans.app)
+ # Set the previous session
+ prev_galaxy_session = trans.get_galaxy_session()
+ if prev_galaxy_session:
+ prev_galaxy_session.is_valid = False
+ # Define a new current_session
+ new_session = create_new_session(trans, prev_galaxy_session, user)
+ trans.set_galaxy_session(new_session)
+ trans.sa_session.add_all((prev_galaxy_session, new_session))
+ trans.sa_session.flush()
+ set_auth_cookie(trans, new_session)
+
+
+def handle_user_logout(trans, logout_all=False):
+ """
+ Logout the current user:
+ - invalidate the current session
+ - create a new session with no user associated
+ """
+ prev_galaxy_session = trans.get_galaxy_session()
+ if prev_galaxy_session:
+ prev_galaxy_session.is_valid = False
+ new_session = create_new_session(trans, prev_galaxy_session, None)
+ trans.set_galaxy_session(new_session)
+ trans.sa_session.add_all((prev_galaxy_session, new_session))
+ trans.sa_session.flush()
+
+ galaxy_user_id = prev_galaxy_session.user_id
+ if logout_all and galaxy_user_id is not None:
+ for other_galaxy_session in trans.sa_session.query(trans.app.model.GalaxySession).filter(
+ and_(
+ trans.app.model.GalaxySession.table.c.user_id == galaxy_user_id,
+ trans.app.model.GalaxySession.table.c.is_valid == true(),
+ trans.app.model.GalaxySession.table.c.id != prev_galaxy_session.id,
+ )
+ ):
+ other_galaxy_session.is_valid = False
+ trans.sa_session.add(other_galaxy_session)
+ trans.sa_session.flush()
+ set_auth_cookie(trans, new_session)
diff --git a/lib/tool_shed/webapp/fast_app.py b/lib/tool_shed/webapp/fast_app.py
index e1fab8be007f..18af5dcb7708 100644
--- a/lib/tool_shed/webapp/fast_app.py
+++ b/lib/tool_shed/webapp/fast_app.py
@@ -1,10 +1,27 @@
+import logging
+import os
+from pathlib import Path
from typing import (
Any,
+ cast,
Dict,
+ Optional,
)
from a2wsgi import WSGIMiddleware
-from fastapi import FastAPI
+from fastapi import (
+ Depends,
+ FastAPI,
+)
+from fastapi.responses import (
+ HTMLResponse,
+ RedirectResponse,
+)
+from fastapi.staticfiles import StaticFiles
+from starlette_graphene3 import (
+ GraphQLApp,
+ make_graphiql_handler,
+)
from galaxy.webapps.base.api import (
add_exception_handler,
@@ -12,6 +29,14 @@
include_all_package_routers,
)
from galaxy.webapps.openapi.utils import get_openapi
+from tool_shed.structured_app import ToolShedApp
+from tool_shed.webapp.api2 import (
+ ensure_valid_session,
+ get_trans,
+)
+from tool_shed.webapp.graphql.schema import schema
+
+log = logging.getLogger(__name__)
api_tags_metadata = [
{
@@ -33,6 +58,90 @@
{"name": "undocumented", "description": "API routes that have not yet been ported to FastAPI."},
]
+# Set this if asset handling should be sent to vite.
+# Run vite with:
+# yarn dev
+# Start tool shed with:
+# TOOL_SHED_VITE_PORT=4040 TOOL_SHED_API_VERSION=v2 ./run_tool_shed.sh
+TOOL_SHED_VITE_PORT: Optional[str] = os.environ.get("TOOL_SHED_VITE_PORT", None)
+TOOL_SHED_USE_HMR: bool = TOOL_SHED_VITE_PORT is not None
+FRONTEND = Path(__file__).parent.resolve() / "frontend"
+FRONTEND_DIST = FRONTEND / "dist"
+
+
+def frontend_controller(app):
+ shed_entry_point = "main.ts"
+ vite_runtime = "@vite/client"
+
+ def index(trans=Depends(get_trans)):
+ if TOOL_SHED_USE_HMR:
+ index = FRONTEND / "index.html"
+ index_html = index.read_text()
+ index_html = index_html.replace(
+ f"""""",
+ f"""""",
+ )
+ else:
+ index = FRONTEND_DIST / "index.html"
+ index_html = index.read_text()
+ ensure_valid_session(trans)
+ cookie = trans.session_csrf_token
+ r: HTMLResponse = cast(HTMLResponse, trans.response)
+ r.set_cookie("session_csrf_token", cookie)
+ return index_html
+
+ return app, index
+
+
+def redirect_route(app, from_url: str, to_url: str):
+ @app.get(from_url)
+ def redirect():
+ return RedirectResponse(to_url)
+
+
+def frontend_route(controller, path):
+ app, index = controller
+ app.get(path, response_class=HTMLResponse)(index)
+
+
+def mount_graphql(app: FastAPI, tool_shed_app: ToolShedApp):
+ context = {
+ "session": tool_shed_app.model.context,
+ "security": tool_shed_app.security,
+ }
+ g_app = GraphQLApp(schema, on_get=make_graphiql_handler(), context_value=context, root_value=context)
+ app.mount("/graphql", g_app)
+ app.mount("/api/graphql", g_app)
+
+
+FRONT_END_ROUTES = [
+ "/",
+ "/admin",
+ "/login",
+ "/register",
+ "/logout_success",
+ "/login_success",
+ "/registration_success",
+ "/help",
+ "/repositories_by_search",
+ "/repositories_by_category",
+ "/repositories_by_category/{category_id}",
+ "/repositories_by_owner",
+ "/repositories_by_owner/{username}",
+ "/repositories/{repository_id}",
+ "/repositories_search",
+ "/_component_showcase",
+ "/user/api_key",
+ "/user/change_password",
+ "/view/{username}",
+ "/view/{username}/{repository_name}",
+ "/view/{username}/{repository_name}/{changeset_revision}",
+]
+LEGACY_ROUTES = {
+ "/user/create": "/register", # for twilltestcase
+ "/user/login": "/login", # for twilltestcase
+}
+
def initialize_fast_app(gx_webapp, tool_shed_app):
app = get_fastapi_instance()
@@ -40,6 +149,27 @@ def initialize_fast_app(gx_webapp, tool_shed_app):
add_request_id_middleware(app)
from .buildapp import SHED_API_VERSION
+ def mount_static(directory: Path):
+ name = directory.name
+ if directory.exists():
+ app.mount(f"/{name}", StaticFiles(directory=directory), name=name)
+
+ if SHED_API_VERSION == "v2":
+ controller = frontend_controller(app)
+ for route in FRONT_END_ROUTES:
+ frontend_route(controller, route)
+
+ for from_route, to_route in LEGACY_ROUTES.items():
+ redirect_route(app, from_route, to_route)
+
+ mount_graphql(app, tool_shed_app)
+
+ mount_static(FRONTEND / "static")
+ if TOOL_SHED_USE_HMR:
+ mount_static(FRONTEND / "node_modules")
+ else:
+ mount_static(FRONTEND_DIST / "assets")
+
routes_package = "tool_shed.webapp.api" if SHED_API_VERSION == "v1" else "tool_shed.webapp.api2"
include_all_package_routers(app, routes_package)
wsgi_handler = WSGIMiddleware(gx_webapp)
diff --git a/lib/tool_shed/webapp/frontend/.eslintignore b/lib/tool_shed/webapp/frontend/.eslintignore
new file mode 100644
index 000000000000..b22c816bfd5e
--- /dev/null
+++ b/lib/tool_shed/webapp/frontend/.eslintignore
@@ -0,0 +1,7 @@
+# don't ever lint node_modules
+node_modules
+# don't lint build output (make sure it's set to your correct build folder name)
+dist
+
+# Ignore codegen aritfacts
+src/gql/*.ts
diff --git a/lib/tool_shed/webapp/frontend/.eslintrc.js b/lib/tool_shed/webapp/frontend/.eslintrc.js
new file mode 100644
index 000000000000..7343e0cc14e1
--- /dev/null
+++ b/lib/tool_shed/webapp/frontend/.eslintrc.js
@@ -0,0 +1,29 @@
+module.exports = {
+ root: true,
+ parser: "vue-eslint-parser",
+ parserOptions: {
+ parser: "@typescript-eslint/parser",
+ // project: ['./tsconfig.json'],
+ },
+ extends: [
+ "plugin:vue/strongly-recommended",
+ "eslint:recommended",
+ "@vue/typescript/recommended",
+ "prettier",
+ "plugin:vuejs-accessibility/recommended",
+ "plugin:@typescript-eslint/eslint-recommended",
+ "plugin:@typescript-eslint/recommended",
+ // More goodies..
+ // "plugin:@typescript-eslint/recommended-requiring-type-checking",
+ ],
+ plugins: ["@typescript-eslint", "prettier", "vuejs-accessibility"],
+ rules: {
+ "prettier/prettier": "error",
+ // not needed for vue 3
+ "vue/no-multiple-template-root": "off",
+ // upgrade warnings for common John problems
+ "@typescript-eslint/no-unused-vars": "error",
+ "vue/require-default-prop": "error",
+ "vue/v-slot-style": "error",
+ },
+}
diff --git a/lib/tool_shed/webapp/frontend/.prettierrc b/lib/tool_shed/webapp/frontend/.prettierrc
new file mode 100644
index 000000000000..0fe7f46213c9
--- /dev/null
+++ b/lib/tool_shed/webapp/frontend/.prettierrc
@@ -0,0 +1,5 @@
+{
+ "tabWidth": 4,
+ "printWidth": 120,
+ "semi": false
+}
diff --git a/lib/tool_shed/webapp/frontend/Makefile b/lib/tool_shed/webapp/frontend/Makefile
new file mode 100644
index 000000000000..b520b0be6844
--- /dev/null
+++ b/lib/tool_shed/webapp/frontend/Makefile
@@ -0,0 +1,22 @@
+GALAXY_ROOT=../../../..
+
+client:
+ yarn build
+
+dev:
+ yarn dev-all
+
+format:
+ yarn format
+
+lint:
+ yarn typecheck && yarn lint
+
+# These next two tasks don't really belong here, but they do demonstrate
+# how to get a test server running and populated with some initial data
+# for the new tool shed frontend.
+run_test_backend:
+ cd $(GALAXY_ROOT); TOOL_SHED_CONFIG_OVERRIDE_BOOTSTRAP_ADMIN_API_KEY=tsadminkey TOOL_SHED_VITE_PORT=4040 TOOL_SHED_API_VERSION=v2 ./run_tool_shed.sh
+
+bootstrap_test_backend:
+ cd $(GALAXY_ROOT); . .venv/bin/activate; python scripts/bootstrap_test_shed.py
diff --git a/lib/tool_shed/webapp/frontend/README.md b/lib/tool_shed/webapp/frontend/README.md
new file mode 100644
index 000000000000..a797a275d079
--- /dev/null
+++ b/lib/tool_shed/webapp/frontend/README.md
@@ -0,0 +1,27 @@
+# Vue 3 + Typescript + Vite
+
+This template should help get you started developing with Vue 3 and Typescript in Vite.
+
+## Recommended IDE Setup
+
+[VSCode](https://code.visualstudio.com/) + [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur). Make sure to enable `vetur.experimental.templateInterpolationService` in settings!
+
+### If Using `
+