mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-30 18:13:23 +08:00
Merge branch 'dev' of https://github.com/galaxyproject/galaxy into dev
This commit is contained in:
@@ -23,7 +23,7 @@ ecosystem.
|
||||
## Mount Functions
|
||||
|
||||
In what most people think of as a "standard" Vue application there would be only one place that Vue
|
||||
is mounted to the HTML environment, and that would be in a main.j or an app.js. Most modern
|
||||
is mounted to the HTML environment, and that would be in a main.js or an app.js. Most modern
|
||||
single-page applications only have one starting point like that.
|
||||
|
||||
However, we are incrementally replacing old Backbone views, so in its current state, Galaxy may have
|
||||
|
||||
@@ -10,7 +10,7 @@ Heading Levels are used by screen readers, and other software, to get a rough id
|
||||
|
||||
- When increasing a heading level, do not skip Levels.
|
||||
|
||||
- Do not use a headings level to determine it's size. Use one of the heading utility classes (e.g. `h-lg`) instead.
|
||||
- Do not use a headings level to determine its size. Use one of the heading utility classes (e.g. `h-lg`) instead.
|
||||
|
||||
- Do not use a heading tag to make a non-heading text large. Use a heading utility class on a `<span>` instead.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
---
|
||||
|
||||
We are using components in two very distinct ways. The first, "normal", kind of component will
|
||||
probably look familiar to anybody whis is already passingly familiar with Vue. Here the relevant
|
||||
probably look familiar to anybody who is already passingly familiar with Vue. Here the relevant
|
||||
information comes in as properties, any internal variables get defined in "data", changes go out as
|
||||
events.
|
||||
|
||||
@@ -18,7 +18,7 @@ events.
|
||||
|
||||
In this example, we've created a component whose job is to deal with loading and updating the doodad
|
||||
object. Notice that there is no markup inside the DoodadProvider other than the explicit renderless
|
||||
component we previously made, but you are free to putput whatever you want in there, accessing the
|
||||
component we previously made, but you are free to put whatever you want in there, accessing the
|
||||
doodad and saveDoddad properties as desired, as well as any other local data with the only
|
||||
restriction that Vue needs a single root element in which to render.
|
||||
|
||||
@@ -57,7 +57,7 @@ update:propname event syntax whenever possible [to facilitate .sync
|
||||
binds](https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier)).
|
||||
|
||||
Whatever happens to that new object is somebody else's job. As soon as you tie the data management
|
||||
to the rendering, the re-usability of your components craters.
|
||||
to the rendering, the re-usability of your components crater.
|
||||
|
||||
## The Provider
|
||||
|
||||
|
||||
@@ -231,7 +231,7 @@ Add spaces between the `script`, `template` and `style` blocks.
|
||||
> </template>
|
||||
>
|
||||
> <style lang="scss" scoped>
|
||||
> // stlye stuff...
|
||||
> // style stuff...
|
||||
> </style>
|
||||
> ```
|
||||
>
|
||||
@@ -245,7 +245,7 @@ Add spaces between the `script`, `template` and `style` blocks.
|
||||
> <!--template stuff-->
|
||||
> </template>
|
||||
> <style lang="scss" scoped>
|
||||
> // stlye stuff...
|
||||
> // style stuff...
|
||||
> </style>
|
||||
> ```
|
||||
|
||||
|
||||
+9
-10
@@ -72,13 +72,13 @@
|
||||
"d3-zoom": "^3.0.0",
|
||||
"d3v3": "npm:d3@3",
|
||||
"date-fns": "^2.30.0",
|
||||
"decode-uri-component": "^0.2.1",
|
||||
"decode-uri-component": "^0.4.1",
|
||||
"dexie": "^3.2.5",
|
||||
"diff": "^8.0.3",
|
||||
"dom-to-image": "^2.6.0",
|
||||
"dompurify": "^3.0.6",
|
||||
"echarts": "^5.5.1",
|
||||
"elkjs": "^0.8.2",
|
||||
"elkjs": "^0.11.1",
|
||||
"events": "^3.3.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"flush-promises": "^1.0.2",
|
||||
@@ -91,7 +91,7 @@
|
||||
"jquery-migrate": "~1.4",
|
||||
"jquery-mousewheel": "^3.1.13",
|
||||
"jquery-ui": "^1.13.2",
|
||||
"katex": "^0.16.22",
|
||||
"katex": "^0.17.0",
|
||||
"linkify-html": "^4.1.1",
|
||||
"linkifyjs": "^4.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
@@ -129,7 +129,7 @@
|
||||
"vscode-languageserver-types": "^3.17.5",
|
||||
"vue": "^2.7.16",
|
||||
"vue-class-component": "^7.2.6",
|
||||
"vue-demi": "0.13.11",
|
||||
"vue-demi": "^0.14.10",
|
||||
"vue-echarts": "^7.0.3",
|
||||
"vue-infinite-scroll": "^2.0.2",
|
||||
"vue-json-pretty": "^1.9.5",
|
||||
@@ -173,11 +173,10 @@
|
||||
"@types/d3": "^7.4.2",
|
||||
"@types/d3-selection": "^3.0.11",
|
||||
"@types/d3-zoom": "^3.0.8",
|
||||
"@types/dompurify": "^3.0.2",
|
||||
"@types/jquery": "^3.5.24",
|
||||
"@types/lodash": "^4.14.200",
|
||||
"@types/lodash.isequal": "^4.5.7",
|
||||
"@types/markdown-it": "^13.0.4",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@typescript-eslint/eslint-plugin": "^6.8.0",
|
||||
"@typescript-eslint/parser": "^6.8.0",
|
||||
"@vitejs/plugin-vue2": "^2.3.4",
|
||||
@@ -186,10 +185,10 @@
|
||||
"@vitest/ui": "^4.1.5",
|
||||
"@vue/test-utils": "^1.3.6",
|
||||
"@vue/tsconfig": "^0.4.0",
|
||||
"autoprefixer": "10.4.16",
|
||||
"autoprefixer": "10.5.0",
|
||||
"buffer": "^6.0.3",
|
||||
"cpy-cli": "^5.0.0",
|
||||
"esbuild": "^0.27.4",
|
||||
"esbuild": "^0.28.1",
|
||||
"eslint": "^8.52.0",
|
||||
"eslint-plugin-compat": "^4.2.0",
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
@@ -198,8 +197,8 @@
|
||||
"eslint-plugin-vuejs-accessibility": "^2.2.0",
|
||||
"fake-indexeddb": "^6.0.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"happy-dom": "^20.0.10",
|
||||
"msw": "^2.3.4",
|
||||
"happy-dom": "20.6.2",
|
||||
"msw": "~2.10.5",
|
||||
"openapi-msw": "^0.7.0",
|
||||
"openapi-typescript": "^7.3.0",
|
||||
"prettier": "^3.6.2",
|
||||
|
||||
@@ -13092,6 +13092,7 @@ export interface components {
|
||||
| "dataverse"
|
||||
| "huggingface"
|
||||
| "iiif"
|
||||
| "mavedb"
|
||||
| "omero"
|
||||
| "ssh";
|
||||
/** Variables */
|
||||
@@ -19995,6 +19996,12 @@ export interface components {
|
||||
PageRevisionDetails: {
|
||||
/** Content */
|
||||
content?: string | null;
|
||||
/**
|
||||
* Content for Editor
|
||||
* @description Raw text contents of the last page revision (type dependent on content_format).
|
||||
* @default
|
||||
*/
|
||||
content_editor: string | null;
|
||||
content_format?: components["schemas"]["PageContentFormat"] | null;
|
||||
/**
|
||||
* Create Time
|
||||
@@ -25381,6 +25388,7 @@ export interface components {
|
||||
| "dataverse"
|
||||
| "huggingface"
|
||||
| "iiif"
|
||||
| "mavedb"
|
||||
| "omero"
|
||||
| "ssh";
|
||||
/** Uri Root */
|
||||
|
||||
Generated
+2101
-1396
File diff suppressed because it is too large
Load Diff
@@ -32,6 +32,10 @@ export const templateTypes: FileSourceTypesDetail = {
|
||||
icon: faGoogleDrive,
|
||||
message: "This is a repository plugin that connects with the commercial Google Drive service.",
|
||||
},
|
||||
mavedb: {
|
||||
icon: faNetworkWired,
|
||||
message: "This is a repository plugin that connects with MaveDB score set files.",
|
||||
},
|
||||
onedrive: {
|
||||
icon: faCloud,
|
||||
message: "This is a repository plugin that connects with Microsoft OneDrive.",
|
||||
|
||||
@@ -16,6 +16,7 @@ const REVISION: PageRevisionDetails = {
|
||||
update_time: "2025-01-01T00:00:00Z",
|
||||
title: "Test Revision",
|
||||
content: "# Title\nOld line 1\nOld line 2",
|
||||
content_editor: "# Title\nOld line 1\nOld line 2",
|
||||
content_format: "markdown",
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import MarkdownIt from "markdown-it";
|
||||
import type Token from "markdown-it/lib/token";
|
||||
import type Token from "markdown-it/lib/token.mjs";
|
||||
import { readonly } from "vue";
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,7 +35,7 @@ These modules must be enabled in the Apache config. The main proxy directives, `
|
||||
Additionally, these directions are written for Apache 2.4+. Apache 2.4 for EL 6 can be obtained from the [CentOS SCLo
|
||||
SIG Repo][sclo-sig-repo]. Otherwise, your system package manager's version of Apache should be suitable.
|
||||
|
||||
Ensure that the `mod_headers`, `mod_rewrite` and `mod_proxy`modules are loaded. Although not
|
||||
Ensure that the `mod_headers`, `mod_rewrite` and `mod_proxy` modules are loaded. Although not
|
||||
required, the configuration examples also use `mod_deflate` and `mod_expires` for increased client/server performance,
|
||||
so these should also be enabled.
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ Currently, we provide two variants of the LDAP authenticator namely, `ldap` and
|
||||
`ldap` is based on the [python-ldap](https://www.python-ldap.org/) module which is a wrapper around the
|
||||
OpenLDAP's client library `libldap`. Currently, `python-ldap` does not provide pre-built Python wheel packages and hence, the OpenLDAP client libraries are needed to install `python-ldap` on the Galaxy server.
|
||||
|
||||
On the otherhand, `ldap3` is a pure-Python implementation of the OpenLDAP client library and has no external dependencies. This package can be installed out-of-the-box, so we recommend to use it when the OpenLDAP client libraries are not available on the Galaxy server.
|
||||
On the other hand, `ldap3` is a pure-Python implementation of the OpenLDAP client library and has no external dependencies. This package can be installed out-of-the-box, so we recommend to use it when the OpenLDAP client libraries are not available on the Galaxy server.
|
||||
|
||||
These same mechanisms can also be configured by proxies serving Galaxy (e.g. nginx or Apache), but configuring them
|
||||
within Galaxy allows users to use the Galaxy UI for logging in instead of relying on a proxy.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Carbon Emissions Reporting
|
||||
|
||||
Galaxy can estimate the carbon footprint of each job you run. The carbon emissions reporting can be
|
||||
configured for each galaxy instance to allow for more accurate results. The following options can
|
||||
configured for each Galaxy instance to allow for more accurate results. The following options can
|
||||
be set:
|
||||
|
||||
* [Geographical server location](#geographical-server-location)
|
||||
@@ -15,7 +15,7 @@ United States [Environmental Protection Agency (EPA)](https://www.epa.gov/energy
|
||||
|
||||
## Geographical server location
|
||||
The `geographical_server_location_code` flag is an [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) string specifying the
|
||||
location of your galaxy instance, with the only exception being `GLOBAL`, which specifies that global average values are to be used.
|
||||
location of your Galaxy instance, with the only exception being `GLOBAL`, which specifies that global average values are to be used.
|
||||
Each valid code corresponds to a location name and a specific carbon intensity value. The corresponding flags
|
||||
(`geographical_server_location_name` and `carbon_intensity`) are automatically set and will be displayed to the client when carbon
|
||||
emissions are reported. The more accurate the location the better the estimates. In all cases, it is assumed that all jobs are run on
|
||||
@@ -25,7 +25,7 @@ a server in the same location as the one specified in `geographical_server_locat
|
||||
|
||||
## Power usage effectiveness
|
||||
[Power usage effectiveness](https://en.wikipedia.org/wiki/Power_usage_effectiveness), or PUE for short, is a ratio specifying
|
||||
how efficiently the data centre hosting your galaxy server used energy. It is essentially your server's 'indirect' energy usage.
|
||||
how efficiently the data centre hosting your Galaxy server used energy. It is essentially your server's 'indirect' energy usage.
|
||||
Galaxy uses a default PUE of `1.67`. If you would like to set a custom value, you can [calculate a PUE value as follows](https://en.wikipedia.org/wiki/Power_usage_effectiveness).
|
||||
|
||||
## Feature toggling
|
||||
|
||||
@@ -113,7 +113,7 @@ galaxy_server% export DRMAA_LIBRARY_PATH=/galaxy/sge/lib/lx24-amd64/libdrmaa.so
|
||||
|
||||
#### DRM Notes
|
||||
|
||||
**Limitations**: The DRMAA runner does not work if Galaxy is configured to run jobs as real user, because in this setting jobs are submitted with an external script, i.e. in an extra DRMAA session, and the session based (python) DRMAA library can only query jobs within the session in which started them. Furthermore, the DRMAA job runner only distinguishes successful and failed jobs and ignores information about possible failure sources, e.g. runtime / memory violation, which could be used for job resubmission. Specialized job runners are abvailable that are not affected by these limitations, e.g. univa and slurm runners.
|
||||
**Limitations**: The DRMAA runner does not work if Galaxy is configured to run jobs as real user, because in this setting jobs are submitted with an external script, i.e. in an extra DRMAA session, and the session based (python) DRMAA library can only query jobs within the session in which started them. Furthermore, the DRMAA job runner only distinguishes successful and failed jobs and ignores information about possible failure sources, e.g. runtime / memory violation, which could be used for job resubmission. Specialized job runners are available that are not affected by these limitations, e.g. univa and slurm runners.
|
||||
|
||||
**TORQUE**: The DRMAA runner can also be used (instead of the [PBS](#pbs) runner) to submit jobs to TORQUE, however, problems have been reported when using the `libdrmaa.so` provided with TORQUE. Using this library will result in a segmentation fault when the drmaa runner attempts to write the job template, and any native job runner options will not be passed to the DRM. Instead, you should compile the [pbs-drmaa](https://apps.man.poznan.pl/trac/pbs-drmaa/wiki) library and use this as the value for `$DRMAA_LIBRARY_PATH`.
|
||||
|
||||
@@ -456,7 +456,7 @@ The `RemoteShell` plugin uses `rsh(1)` to connect to a remote system and execute
|
||||
: Remote system hostname to log in to.
|
||||
|
||||
``shell_rsh``
|
||||
: ``rsh``-like command to excute (e.g. ``<param id="shell_rsh">/opt/example/bin/remsh</param>``) - just defaults to ``rst``.
|
||||
: ``rsh``-like command to execute (e.g. ``<param id="shell_rsh">/opt/example/bin/remsh</param>``) - just defaults to ``rst``.
|
||||
|
||||
The `RemoteShell` parameters translate to a command line of `% <shell_rsh> [-l <shell_username>] <shell_hostname> "<remote_command_with_args>"`, where the inclusion of `-l` is dependent on whether `shell_username` is set. Alternate values for `shell_rsh` must be compatible with this syntax.
|
||||
|
||||
@@ -571,7 +571,7 @@ Since this is a complex problem, the current solution does have some caveats:
|
||||
* All of the datasets stored in Galaxy will have to be readable on the underlying filesystem by all Galaxy users. Said users need not have direct access to any systems which mount these filesystems, only the ability to run jobs on clusters that mount them. But I expect that in most environments, users will have the ability to submit jobs to these clusters or log in to these clusters outside of Galaxy, so this will be a security concern to evaluate for most environments.
|
||||
* *Technical details* - Since Galaxy maintains dataset sharing internally and all files are owned by the Galaxy user, when running jobs only under a single user, permissions can be set such that only the Galaxy user can read all datasets. Since the dataset may be shared by multiple users, it is not suitable to simply change ownership of inputs before a job runs (what if another user tried to use the same dataset as an input during this time?). This could possibly be solved if Galaxy had tight control over extended ACLs on the file, but since many different ACL schemes exist, Galaxy would need a module for each scheme to be supported.
|
||||
* The real user system works by changing ownership of the job's working directory to the system prior to running the job, and back to the Galaxy user once the job has completed. It does this by executing a site-customizable script via [sudo](https://www.sudo.ws/).
|
||||
* Two possibilities to determine the system user that corresponds to a galaxy user are implemented: i) the user whos name matches the Galaxy user's email address (with the @domain stripped off) and ii) the user whos name is equal to the galaxy user name. Until release 17.05 only the former option is available. The latter option is suitable for Galaxy installations that user external authentification (e.g. LDAP) against a source that is also the source of the system users.
|
||||
* Two possibilities to determine the system user that corresponds to a galaxy user are implemented: i) the user whose name matches the Galaxy user's email address (with the @domain stripped off) and ii) the user whos name is equal to the galaxy user name. Until release 17.05 only the former option is available. The latter option is suitable for Galaxy installations that user external authentification (e.g. LDAP) against a source that is also the source of the system users.
|
||||
* The script accepts a path and does nothing to ensure that this path is a Galaxy working directory per default (and not at all up to release 17.05). So anyone who has access to the Galaxy user could use this script and sudo to change the ownership of any file or directory. Furthermore, anyone with write access to the script could introduce arbitrary (harmful) code -- so it might be a good idea to give write access only to trustworthy users, e.g., root.
|
||||
|
||||
### Configuration
|
||||
@@ -605,7 +605,7 @@ For Galaxy releases > 17.05, the sudo call has been moved to `galaxy.yml` and is
|
||||
drmaa_external_runjob_script: sudo -E .venv/bin/python scripts/drmaa_external_runner.py --assign_all_groups
|
||||
```
|
||||
|
||||
Also for Galaxy releases > 17.05: In order to allow `external_chown_script.py` to chown only path below certain entry points the variable `ALLOWED_PATHS` in the python script can be adapted. It is sufficient to include the directorries `job_working_directory` and `new_file_path` as configured in `galaxy.yml`.
|
||||
Also for Galaxy releases > 17.05: In order to allow `external_chown_script.py` to chown only path below certain entry points the variable `ALLOWED_PATHS` in the python script can be adapted. It is sufficient to include the directories `job_working_directory` and `new_file_path` as configured in `galaxy.yml`.
|
||||
|
||||
It is also a good idea to make sure that only trusted users, e.g. root, have write access to all three scripts.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
Conda for Tool Dependencies
|
||||
===========================
|
||||
|
||||
.. note:: This document describes configuring Galaxy using YAML based configuraiton
|
||||
.. note:: This document describes configuring Galaxy using YAML based configuration
|
||||
options. For Galaxy instances before version 18.01, `this variant
|
||||
<https://docs.galaxyproject.org/en/release_17.09/admin/conda_faq.html>`__ of this
|
||||
document will be more directly relatable.
|
||||
|
||||
@@ -64,7 +64,7 @@ Additional configuration files and their purposes are:
|
||||
internally.
|
||||
- ``build_sites.yml``: Controls which display applications are available and their configuration paths
|
||||
- ``containers_conf.yml``: Configures the beta Galaxy containers interface, currently only used by Galaxy Interactive
|
||||
Environments, and only neccesary for Docker Swarm support.
|
||||
Environments, and only necessary for Docker Swarm support.
|
||||
- ``dependency_resolvers_conf.xml``: Describes how Galaxy tools (which are typically just descriptions of how to run a
|
||||
particular command line tool) should locate their dependencies (the command line tool) that are not part of the tool.
|
||||
See the `Dependency Resolvers documentation <dependency_resolvers>` for more.
|
||||
|
||||
@@ -364,7 +364,7 @@ admins might want to control.
|
||||
|
||||
.. note::
|
||||
|
||||
For the execution of jobs Galaxy already implement the `support for using
|
||||
For the execution of jobs Galaxy already implements the `support for using
|
||||
tarballs of container images
|
||||
<https://github.com/galaxyproject/galaxy/blob/c517e805771cc16807dfe675075a13fe6343f01f/lib/galaxy/tool_util/deps/container_classes.py#L319>`_.
|
||||
from ``container_image_cache_path`` (set in galaxy.yml) or the destination
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Galaxy has countless ways for users to connect with things that might be considered their "data" - file sources (aka "remote files"), object stores (aka "storage locations"), data libraries, the upload API, visualizations, display applications, custom tools, etc...
|
||||
|
||||
This document is going to discuss two of these (file sources and object stores) that are most important Galaxy administrators and how to build Galaxy configurations that allow administrators to let users tie into various pieces of infrastructure (local and publicly available).
|
||||
This document is going to discuss two of these (file sources and object stores) that are most important to Galaxy administrators and how to build Galaxy configurations that allow administrators to let users tie into various pieces of infrastructure (local and publicly available).
|
||||
|
||||
```{contents} Table of Contents
|
||||
:depth: 4
|
||||
@@ -17,7 +17,7 @@ Galaxy object stores (called "storage locations" in the UI) store datasets and g
|
||||
|
||||
Some of Galaxy's most updated and complete administrator documentation can be found in configuration sample files - this is definitely the case for object stores and file sources. The relevant sample configuration files include [file_sources_conf.yml.sample](https://github.com/galaxyproject/galaxy/blob/dev/lib/galaxy/config/sample/file_sources_conf.yml.sample) and [object_store_conf.sample.yml](https://github.com/galaxyproject/galaxy/blob/dev/lib/galaxy/config/sample/object_store_conf.sample.yml).
|
||||
|
||||
File sources and object stores configured with the above files essentially are available to all users of your Galaxy instance - hence this document describes them as "global" file sources and object stores. File source configurations do allow some templating that does allow the a global file source to be materialized differently for different users. For instance, you as an admin may setup a Dropbox file source and may explicitly add custom user properties that allow that single Dropbox file source to read from a user's preferences. Since there is just one Dropbox service and most people only have a single Dropbox account, this use case can be somewhat adequately addressed by the global file source and the global user preferences file. For a use case like Amazon S3 buckets though for instance, a single bucket file source that is parameterized one way is probably more clearly inadequate. For instance, users would very likely want to attach different buckets for different projects. Additionally, the Galaxy user interface doesn't tie the user preferences to the particular file source and so this method introduces a huge education burden on your Galaxy instance. Finally, the templating available to file sources are not available for object stores - and allowing users to describe how they would like datasets stored and to pay for their own dataset storage are important use cases.
|
||||
File sources and object stores configured with the above files essentially are available to all users of your Galaxy instance - hence this document describes them as "global" file sources and object stores. File source configurations do allow some templating that does allow a global file source to be materialized differently for different users. For instance, you as an admin may setup a Dropbox file source and may explicitly add custom user properties that allow that single Dropbox file source to read from a user's preferences. Since there is just one Dropbox service and most people only have a single Dropbox account, this use case can be somewhat adequately addressed by the global file source and the global user preferences file. For a use case like Amazon S3 buckets though for instance, a single bucket file source that is parameterized one way is probably more clearly inadequate. For instance, users would very likely want to attach different buckets for different projects. Additionally, the Galaxy user interface doesn't tie the user preferences to the particular file source and so this method introduces a huge education burden on your Galaxy instance. Finally, the templating available to file sources are not available for object stores - and allowing users to describe how they would like datasets stored and to pay for their own dataset storage are important use cases.
|
||||
|
||||
This document is going to describe Galaxy configuration template libraries that allow the
|
||||
administrator to setup templates for file sources and object stores that your users may instantiate
|
||||
|
||||
@@ -16,7 +16,7 @@ In order to understand the naming and structure of these three components it mig
|
||||
to look in the history. Tool data was organized in tabular `loc` that contained metadata and paths
|
||||
of the data. Those files were installed with the tool and could be accessed with the
|
||||
[`from_file`](https://docs.galaxyproject.org/en/master/dev/schema.html#from-file) mechanism from tools.
|
||||
Since each tool version had it's own `loc` file the maintenance was difficult. With tool data tables
|
||||
Since each tool version had its own `loc` file the maintenance was difficult. With tool data tables
|
||||
an additional abstraction layer was introduced that is used from tools via
|
||||
[`from_datatable`](https://docs.galaxyproject.org/en/master/dev/schema.html#from-data-table).
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ Galaxy's data model includes the [galaxy model](https://github.com/galaxyproject
|
||||
These two models may be persisted in one combined database (which is the default) or two separate databases (which is enabled by setting the
|
||||
[`install_database_connection`](https://github.com/galaxyproject/galaxy/blob/dev/lib/galaxy/webapps/galaxy/config_schema.yml#L157) configuration option).
|
||||
|
||||
These models are represented by migration [branches](https://alembic.sqlalchemy.org/en/latest/branches.html) (versioning lineages with a common base) labeled as *gxy* for the galaxy model and *tsi* for the install model. If both models are hosted in the same databases, the branches will share the same Alembic version table; otherwise, each database has its own version table.
|
||||
These models are represented by migration [branches](https://alembic.sqlalchemy.org/en/latest/branches.html) (versioning lineages with a common base) labeled as *gxy* for the galaxy model and *tsi* for the install model. If both models are hosted in the same database, the branches will share the same Alembic version table; otherwise, each database has its own version table.
|
||||
|
||||
Each branch has its own version history, represented by revision scripts located in the branch version directory (`migrations/alembic/versions_gxy` for *gxy* and `migrations/alembic/versions_tsi` for *tsi*).
|
||||
|
||||
|
||||
@@ -294,7 +294,7 @@ mapping_files
|
||||
|
||||
skip_availability_check
|
||||
Set to ``true`` to skip filtering module dependencies using ``module avail`` and attempt to load each
|
||||
module listed in the depedencies (default: ``false``).
|
||||
module listed in the dependencies (default: ``false``).
|
||||
This setting should be used when during dependency resolution modules are not listed in ``module avail``,
|
||||
but can be expected to be present when using ``module load`` in the job script, such as child modules
|
||||
in hierarchical module systems. Note: the order in which dependencies are listed could be important.
|
||||
@@ -342,7 +342,7 @@ default_indicator
|
||||
|
||||
skip_availability_check
|
||||
Set to ``true`` to skip filtering module dependencies using ``module avail`` and attempt to load each
|
||||
module listed in the depedencies (default: ``false``).
|
||||
module listed in the dependencies (default: ``false``).
|
||||
This setting should be used when during dependency resolution modules are not listed in ``module avail``,
|
||||
but can be expected to be present when using ``module load`` in the job script, such as child modules
|
||||
in hierarchical module systems. Note: the order in which dependencies are listed could be important.
|
||||
@@ -376,6 +376,6 @@ It is highly experimental, undocumented, and unmaintained, and likely to be drop
|
||||
Brewed Tool Shed Package Resolver
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``brewed_tool_shed`` dependency resolver was an attmept to resolve tool shed packages that had been auto converted
|
||||
The ``brewed_tool_shed`` dependency resolver was an attempt to resolve tool shed packages that had been auto converted
|
||||
to the tool shed. It is highly experimental, undocumented, unmaintained, and will almost certainly be removed from the
|
||||
code base.
|
||||
|
||||
@@ -697,7 +697,7 @@
|
||||
:Description:
|
||||
Location of files available for a short time as downloads (short
|
||||
term storage). This directory is exclusively used for serving
|
||||
dynamically generated downloadable content. Galaxy may uses the
|
||||
dynamically generated downloadable content. Galaxy may use the
|
||||
new_file_path parameter as a general temporary directory and that
|
||||
directory should be monitored by a tool such as tmpwatch in
|
||||
production environments. short_term_storage_dir on the other hand
|
||||
@@ -1008,7 +1008,7 @@
|
||||
|
||||
:Description:
|
||||
XML config file that contains data table entries for the
|
||||
ToolDataTableManager. This file is manually # maintained by the
|
||||
ToolDataTableManager. This file is manually maintained by the
|
||||
Galaxy administrator (.sample used if default does not exist).
|
||||
The value of this option will be resolved with respect to
|
||||
<config_dir>.
|
||||
@@ -1288,7 +1288,7 @@
|
||||
destination level for heterogeneous clusters. conda job resolution
|
||||
requires bash or zsh so if this is switched to /bin/sh for
|
||||
instance - conda resolution should be disabled. Containerized jobs
|
||||
always use /bin/sh - so more maximum portability tool authors
|
||||
always use /bin/sh - for maximum portability tool authors
|
||||
should assume generated commands run in sh.
|
||||
:Default: ``/bin/bash``
|
||||
:Type: str
|
||||
@@ -1323,7 +1323,7 @@
|
||||
|
||||
:Description:
|
||||
Set this to true to attempt to resolve bio.tools metadata for
|
||||
tools for tool not resovled via biotools_content_directory.
|
||||
tools for tool not resolved via biotools_content_directory.
|
||||
:Default: ``false``
|
||||
:Type: bool
|
||||
|
||||
|
||||
@@ -279,7 +279,7 @@ def ncbi_blastn_wrapper(app, user_email):
|
||||
```
|
||||
|
||||
|
||||
The first example above delegates to the PBS job runner and allocates extra walltime for larger input files (based on tool input parameter named `query`). The second example delegates to the DRMAA job runner and assigns users in the in the admin list to a special project (perhaps configured to have a higher priority or extended walltime).
|
||||
The first example above delegates to the PBS job runner and allocates extra walltime for larger input files (based on tool input parameter named `query`). The second example delegates to the DRMAA job runner and assigns users in the admin list to a special project (perhaps configured to have a higher priority or extended walltime).
|
||||
|
||||
The above examples demonstrate that the dynamic job destination framework will pass in the arguments to your function that are needed based on the argument names. The valid argument names at this time are:
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ We removed support for running Galaxy under uWSGI in Galaxy release 22.05.
|
||||
We made this move because uWSGI support for newer Python versions has been problematic and because
|
||||
we have modernized the API portion of our backend. The Galaxy web application is now an ASGI application
|
||||
driven by [FastAPI](https://fastapi.tiangolo.com/) and [Starlette](https://www.starlette.io/).
|
||||
This enables numerous important features that were not be possible (or were more complicated to set up)
|
||||
This enables numerous important features that were not possible (or were more complicated to set up)
|
||||
otherwise. Importantly, [FastAPI's documentation](https://fastapi.tiangolo.com/tutorial/) is very good and
|
||||
should increase developer productivity.
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ Instructions for [proxying with Apache](apache.md) are also available.
|
||||
```{include} _inc_proxy_prereq.md
|
||||
```
|
||||
|
||||
### NGINX Proxy Prerequisities
|
||||
### NGINX Proxy Prerequisites
|
||||
|
||||
If you are **not** planning to use the recommended [tus.io method](#receiving-files-via-the-tus-protocol) to handle file uploads but want to use nginx to handle uploads, you will (most likely) not be able to use your package manager's version of nginx. The [Receiving Files With NGINX](#receiving-files-with-nginx-legacy) section explains this in detail and provides some options for installing *nginx + upload module* packages maintained by the Galaxy Committers Team.
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ To get started with setting up local data, please see [Data Integration](https:/
|
||||
* The `galaxy_dist/tool-data/` directory contains a set of sample location (`<data_label>.loc`) files that describe the metadata and path to local data and indexes.
|
||||
* Installed tool packages from the [Tool Shed](https://galaxyproject.org/toolshed/) may also include location files.
|
||||
* Comments in location files explain the expected format.
|
||||
* [Data Integration](https://galaxyproject.org/admin/data-integration/) explain how to obtain, create, or rysnc many common data and indexes. See an individual Tool Shed repository's documentation for more details.
|
||||
* [Data Integration](https://galaxyproject.org/admin/data-integration/) explain how to obtain, create, or rsync many common data and indexes. See an individual Tool Shed repository's documentation for more details.
|
||||
|
||||
### Enable upload via FTP
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ If Galaxy complains about the version of Python you are using:
|
||||
```sh
|
||||
% rm -rf /path/to/galaxy/database/compiled_templates/
|
||||
```
|
||||
These templated will be regenerated automatically when starting Galaxy.
|
||||
These templates will be regenerated automatically when starting Galaxy.
|
||||
|
||||
5. Start Galaxy again.
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
:Description:
|
||||
Database connection. Galaxy Reports are intended for production
|
||||
Galaxy instances, so sqlite (and the default value below) is not
|
||||
supported. An SQLAlchemy connection string should be used specify
|
||||
supported. An SQLAlchemy connection string should be used to specify
|
||||
an external database.
|
||||
:Default: ``sqlite:///./database/universe.sqlite?isolation_level=IMMEDIATE``
|
||||
:Type: str
|
||||
|
||||
@@ -411,7 +411,7 @@ If you omit the `processes` argument this will default to a single process.
|
||||
You can further customize the handler names using the `name_template` section,
|
||||
for a complete example see [this gravity test case](https://github.com/galaxyproject/gravity/blob/a00df1c671fdc01e3fbc42f64a851a45a37a1870/tests/test_process_manager.py#L28).
|
||||
|
||||
You can define arbitrary environment variables for dynamic handlers using the ``environnment`` key on a handler
|
||||
You can define arbitrary environment variables for dynamic handlers using the ``environment`` key on a handler
|
||||
definition:
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -12,7 +12,7 @@ Galaxy. Thus it is possible that a tool modifies data in Galaxy's ``file_path``.
|
||||
Examples of such potential changes are:
|
||||
|
||||
- Creation of additional files, e.g. indices, which is a problem for cleaning up data, because Galaxy does not know about these files.
|
||||
- Removal of tool input or output files. This will create problems with other tools using these datasets (note that most tool repositories use CI tests to to avoid this, but the problem may still occur).
|
||||
- Removal of tool input or output files. This will create problems with other tools using these datasets (note that most tool repositories use CI tests to avoid this, but the problem may still occur).
|
||||
|
||||
Note that a tool only knows the paths to its inputs and outputs, but if using the default configuration for other paths (e.g. the configuration directory) also these paths can be calculated and accessed.
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ Although any FTP server should work, our public site uses ProFTPD. You'll need
|
||||
* mod_sql_postgres or mod_sql_mysql
|
||||
* mod_sql_passwd
|
||||
|
||||
We compile by hand using the following configure arguments (OpenSSL is prebuilt and statically linked), you should read the INSTALL file that come with the proftpd source distribution. At least you should consider if you need to use any of these options "install_user=<user> install_group=<group> ./configure --sysconfdir=/etc --localstatedir=/var":
|
||||
We compile by hand using the following configure arguments (OpenSSL is prebuilt and statically linked), you should read the INSTALL file that comes with the proftpd source distribution. At least you should consider if you need to use any of these options "install_user=<user> install_group=<group> ./configure --sysconfdir=/etc --localstatedir=/var":
|
||||
|
||||
```console
|
||||
./configure --prefix=/foo --disable-auth-file --disable-ncurses --disable-ident --disable-shadow --enable-openssl --with-modules=mod_sql:mod_sql_postgres:mod_sql_passwd --with-includes=/usr/postgres/9.1-pgdg/include:`pwd`/../openssl/.openssl/include --with-libraries=/usr/postgres/9.1-pgdg/lib/64:`pwd`/../openssl/.openssl/lib
|
||||
|
||||
@@ -10,7 +10,7 @@ and enabling a Galaxy User to gain access to content inside in real-time.
|
||||
How Galaxy InteractiveTools work
|
||||
--------------------------------
|
||||
|
||||
A InteractiveTool is defined in the same familiar way as standard Galaxy Tools,
|
||||
An InteractiveTool is defined in the same familiar way as standard Galaxy Tools,
|
||||
but are specified with ``tool_type="interactive"``, and providing additional
|
||||
entry point information:
|
||||
|
||||
@@ -202,7 +202,7 @@ An example ``job_conf.yml`` file as seen in ``config/job_conf.yml.interactivetoo
|
||||
default_destination_id: local
|
||||
|
||||
|
||||
The Galaxy currently contains a sizable collection of **InteractiveTools** directly in the
|
||||
Galaxy currently contains a sizable collection of **InteractiveTools** directly in the
|
||||
code base. To be enabled, they need to be commented in or added to the ``config/tool_conf.xml``:
|
||||
|
||||
.. code-block:: xml
|
||||
@@ -213,7 +213,7 @@ code base. To be enabled, they need to be commented in or added to the ``config/
|
||||
</toolbox>
|
||||
|
||||
|
||||
A InteractiveTool is defined in the same familiar way as standard Galaxy Tools,
|
||||
An InteractiveTool is defined in the same familiar way as standard Galaxy Tools,
|
||||
but are specified with ``tool_type="interactive"``, and providing additional
|
||||
entry point information:
|
||||
|
||||
@@ -268,7 +268,7 @@ server within an InteractiveTool container now must serve the contents under a p
|
||||
2. Absolute links. Unfortunately many relevant services are implemented with absolute links, i.e. starting
|
||||
at the top-level ``/``. For such InteractiveTools to work with path-based URLs the contained web server
|
||||
needs to be configured with the path prefix/entry point path under which the content should be served. Two issues then
|
||||
needs to be considered:
|
||||
need to be considered:
|
||||
|
||||
a. How to inject the path prefix into the InteractiveTool at run-time?
|
||||
|
||||
@@ -326,7 +326,7 @@ server within an InteractiveTool container now must serve the contents under a p
|
||||
When ``requires_path_in_url="True"`` in the ``entry_point`` tag, the InteractiveTool proxy service forwards the HTTP requests
|
||||
with the full path intact.
|
||||
|
||||
Both values of ``requires_path_in_url`` can be combined with both injection mechanisms, leading two four configuration variants
|
||||
Both values of ``requires_path_in_url`` can be combined with both injection mechanisms, leading to four configuration variants
|
||||
for path-based InteractiveTools. Choosing the correct one depends on the implementation of the web server contained in the
|
||||
InteractiveTool and can be a bit tricky to get correct. In some cases, none of these options will work. One solution can then
|
||||
be to configure another highly customized proxy web server within the InteractiveTool, e.g. using NGINX.
|
||||
@@ -395,7 +395,7 @@ In both nginx config examples, you might want to replace localhost with your ser
|
||||
Job runner configuration in production
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
InteractiveTools have been enabled for the Condor, Slurm, Pulsar and Kuberneters job runner.
|
||||
InteractiveTools have been enabled for the Condor, Slurm, Pulsar and Kubernetes job runner.
|
||||
A destination configuration for Condor may look like this:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
@@ -18,7 +18,7 @@ Job metrics can be collected for Pulsar jobs - but there is added complexity. Th
|
||||
|
||||
See `job_metrics_conf.xml.sample` for the most updated information.
|
||||
|
||||
The `core` plugin is one enabled by default and it is a cross-platform plugin used to caputre job run time and core allocation. `env` is the only other cross-platform plugin (should work with any *nix) and can be used to record all environment variables set at job runtime or just specific variables (e.g. `PATH`) - this can potentially be useful for debugging cluster issues.
|
||||
The `core` plugin is one enabled by default and it is a cross-platform plugin used to capture job run time and core allocation. `env` is the only other cross-platform plugin (should work with any *nix) and can be used to record all environment variables set at job runtime or just specific variables (e.g. `PATH`) - this can potentially be useful for debugging cluster issues.
|
||||
|
||||
There are a number of remaining Linux-only plugins - there include `cpuinfo` and `meminfo` to collect CPU and memory about the node the Galaxy job runs on and `uname` to likewise collect host and operating system information.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Tiny plugin interface to extend the Galaxy client.
|
||||
|
||||
Galaxy webhooks provides a simple way of inserting icons, links, or other HTML elements into predefined locations.
|
||||
For this Galaxy provides some entry points which can be used to extend the client with content. This content
|
||||
can consists out of simple HTML, JS or dynamically generated content from a python function.
|
||||
can consist of simple HTML, JS or dynamically generated content from a python function.
|
||||
|
||||
Please note that the webhooks interface is new and can change in the coming releases. Consider it as beta as we don't
|
||||
make promises to keep the API stable at the moment.
|
||||
@@ -82,7 +82,7 @@ The configuration file is just a .yml (or .yaml) file with a few options. The fo
|
||||
- **activate** - *true* or *false* - whether show the plugin on a page or not
|
||||
- **icon** Icon to show (if masthead)
|
||||
- **tooltip** - Tooltip to show on hover
|
||||
- **function** - A javascript function to be executed. Any calls to Galaxy APIs should be sure to use Galaxy.root when constructing the URL to ensure compatability across Galaxy deployments.
|
||||
- **function** - A javascript function to be executed. Any calls to Galaxy APIs should be sure to use Galaxy.root when constructing the URL to ensure compatibility across Galaxy deployments.
|
||||
|
||||
All other options can be anything used by the plugin and accessed later via *webhook.config['...']*.
|
||||
|
||||
|
||||
@@ -40,9 +40,9 @@ To safely delete unused histories and their associated records, please use the `
|
||||
Deleting old galaxy_session records
|
||||
-----------------------------------
|
||||
|
||||
Each time Galaxy is accessed, a galaxy_session record is created, even when the user is annonymous. Over time, Galaxy accumulates such records. Deleting such records will declutter the database and free up space.
|
||||
Each time Galaxy is accessed, a galaxy_session record is created, even when the user is anonymous. Over time, Galaxy accumulates such records. Deleting such records will declutter the database and free up space.
|
||||
|
||||
To safely delete such records, please use the galaxy-delete-sessions script. By default, a galaxy_session record should be at least a month old to be considered safe to delete (which is determinded by the value of its ``update_time`` field).
|
||||
To safely delete such records, please use the galaxy-delete-sessions script. By default, a galaxy_session record should be at least a month old to be considered safe to delete (which is determined by the value of its ``update_time`` field).
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ While the structure is similar, several key differences exist:
|
||||
To enable this feature:
|
||||
|
||||
1. Set `enable_beta_tool_formats: true` in your Galaxy configuration.
|
||||
2. Create a role of type `Custom Tool Execution` in the admin user interdace.
|
||||
2. Create a role of type `Custom Tool Execution` in the admin user interface.
|
||||
3. Assign users or groups to this role.
|
||||
|
||||
## Sharing User-Defined Tools
|
||||
@@ -88,7 +88,7 @@ These tools can also be exported to disk and loaded like regular tools, enabling
|
||||
|
||||
## Security considerations
|
||||
|
||||
User-defined tools share the same security risks as interactive tools..
|
||||
User-defined tools share the same security risks as interactive tools.
|
||||
See https://training.galaxyproject.org/training-material/topics/admin/tutorials/interactive-tools/tutorial.html#securing-interactive-tools for an extended discussion,
|
||||
and see https://github.com/galaxyproject/galaxy/blob/dev/test/integration/embedded_pulsar_job_conf.yml#L29 for a simple example that uses embedded pulsar to isolate mounts and disables network access.
|
||||
While the feature is in beta we recommend that only trusted users are allowed to use this feature.
|
||||
|
||||
@@ -44,7 +44,7 @@ of the static checking that can be done for the Galaxy backend.
|
||||
To further document your API and allow other developers to see how it functions,
|
||||
new API functionality should include functional tests. These functional
|
||||
tests should be implemented in Python and added to either the API test suite
|
||||
or the integration test suite. Checkout the :doc:`Writing Tests for Galaxy <writing_tests>`
|
||||
or the integration test suite. Check out the :doc:`Writing Tests for Galaxy <writing_tests>`
|
||||
documentation for more information on developing tests for Galaxy.
|
||||
|
||||
.. note::
|
||||
@@ -131,7 +131,7 @@ in :mod:`galaxy.webapps.galaxy.api` receive or produce Pydantic_
|
||||
models and pass them to manager components defined in
|
||||
:mod:`galaxy.managers`. The manager is then responsible for
|
||||
interacting with the whole backend or specific components to
|
||||
fullfil the desired request.
|
||||
fulfill the desired request.
|
||||
|
||||
Galaxy work contexts provide a way for API requests (and other
|
||||
components) to describe what they require from the framework
|
||||
|
||||
@@ -508,7 +508,7 @@ Data Manager can add entries to one or more Tool Data Tables. For
|
||||
each Tool Data Table under consideration, the expected output entry
|
||||
columns, and how to handle the Data Manager Tool results, are defined.
|
||||
|
||||
Upon installation, Galaxy will recursivly search for data_manager_conf.xml in the repository.
|
||||
Upon installation, Galaxy will recursively search for data_manager_conf.xml in the repository.
|
||||
data_manager_conf.xml should be located in the root of the repository but can be located in a sub-directory.
|
||||
The first instance of data_manager_conf.xml found will be used.
|
||||
This file defines the paths to the data manager tools within the repository directory structure.
|
||||
@@ -648,7 +648,7 @@ the ``extra_files_path`` of ``out_file``.
|
||||
</when>
|
||||
<when value="directory">
|
||||
<param type="text" name="fasta_filename" value="" label="Full path to FASTA File on disk" optional="False" />
|
||||
<param type="boolean" name="create_symlink" truevalue="create_symlink" falsevalue="copy_file" label="Create symlink to orignal data instead of copying" checked="False" />
|
||||
<param type="boolean" name="create_symlink" truevalue="create_symlink" falsevalue="copy_file" label="Create symlink to original data instead of copying" checked="False" />
|
||||
</when>
|
||||
</conditional>
|
||||
</inputs>
|
||||
|
||||
@@ -40,5 +40,5 @@ should work fine.
|
||||
2. Re-start VS Code
|
||||
3. Add a breakpoint somewhere in your code
|
||||
4. Select Run and Debug on Activity Bar, on the far left hand side. In the RUN AND DEBUG drop down, select `GalaxyFastAPI uvicorn`, and click the Start Debugging button (green arrow)
|
||||
5. Galaxy should stop right on the break point you added.
|
||||
5. Galaxy should stop right on the breakpoint you added.
|
||||
6. Enjoy your debugging session :-)
|
||||
|
||||
@@ -6,7 +6,7 @@ default LocalRunner). If you have a MacOS machine, you need to create a Linux VM
|
||||
VirtualBoxVM and run the instructions in the VM. For reference, I created a 64-bit Linux VM with
|
||||
4096 MB of RAM and 32 GB of disk space (all other parameters were left as default) and managed to
|
||||
successfully run the instructions. If using a Linux VM, you may need to install Git, Python, VSCode,
|
||||
and Docker (if using Irods storage). Finally, These instructions are intended for developers who
|
||||
and Docker (if using Irods storage). Finally, these instructions are intended for developers who
|
||||
want to debug their Galaxy instance and are not meant for deployment purposes (We have nice Ansible
|
||||
playbooks for that).
|
||||
|
||||
@@ -73,4 +73,4 @@ playbooks for that).
|
||||
|
||||
6. Follow the instructions [here](debugging_galaxy) to setup VSCode for debugging.
|
||||
|
||||
Enjoy debugging session your Galaxy instance backed by a Slurm cluster!
|
||||
Enjoy debugging your Galaxy instance backed by a Slurm cluster!
|
||||
|
||||
@@ -77,13 +77,13 @@ find the right documentation for a given test one wishes to write.
|
||||
- **Client/ES6**
|
||||
|
||||
These tests should be placed in ``client/src`` directly and executed
|
||||
via Vitest. Checkout [Frontend/ES6 Unit Tests](#es6_unit) below for more
|
||||
via Vitest. Check out [Frontend/ES6 Unit Tests](#es6_unit) below for more
|
||||
information.
|
||||
|
||||
- **Backend/Python**
|
||||
|
||||
These tests should be placed in ``test/unit`` or doctests and
|
||||
executed via pytest. Checkout [Backend/Python Unit Tests](#python_unit) below for more
|
||||
executed via pytest. Check out [Backend/Python Unit Tests](#python_unit) below for more
|
||||
information.
|
||||
|
||||
- **Yes**
|
||||
@@ -1212,8 +1212,8 @@ see the [Browser Automation README](https://github.com/galaxyproject/galaxy/blob
|
||||
### Jupyter + Selenium
|
||||
|
||||
Jupyter can leveraged to develop Selenium test cases interactively,
|
||||
checkout out the [``galaxy_test.selenium.jupyter`` documentation](../lib/galaxy_test.selenium.jupyter)
|
||||
for more a full discussion of this.
|
||||
check out the [``galaxy_test.selenium.jupyter`` documentation](../lib/galaxy_test.selenium.jupyter)
|
||||
for a full discussion of this.
|
||||
|
||||
### API Abstractions Available
|
||||
|
||||
|
||||
@@ -31,8 +31,11 @@ from galaxy.schema.fetch_data import (
|
||||
from galaxy.schema.invocation import InvocationSerializationParams
|
||||
from galaxy.schema.schema import (
|
||||
CreateHistoryPayload,
|
||||
CreatePagePayload,
|
||||
DatasetSourceType,
|
||||
InvocationIndexPayload,
|
||||
PageIndexQueryPayload,
|
||||
UpdatePagePayload,
|
||||
WorkflowIndexPayload,
|
||||
)
|
||||
from galaxy.schema.workflows import InvokeWorkflowPayload
|
||||
@@ -72,6 +75,7 @@ class AgentOperationsManager:
|
||||
self._dataset_collections_service: Optional[Any] = None
|
||||
self._dynamic_tools_manager: Optional[Any] = None
|
||||
self._file_source_instances_manager: Optional[Any] = None
|
||||
self._pages_service: Optional[Any] = None
|
||||
|
||||
def _encode_id(self, value: int) -> str:
|
||||
return self.trans.security.encode_id(value)
|
||||
@@ -173,6 +177,14 @@ class AgentOperationsManager:
|
||||
self._file_source_instances_manager = self.app[FileSourceInstancesManager]
|
||||
return self._file_source_instances_manager
|
||||
|
||||
@property
|
||||
def pages_service(self):
|
||||
if self._pages_service is None:
|
||||
from galaxy.webapps.galaxy.services.pages import PagesService
|
||||
|
||||
self._pages_service = self.app[PagesService]
|
||||
return self._pages_service
|
||||
|
||||
def connect(self) -> dict[str, Any]:
|
||||
config = self.app.config
|
||||
user = self.trans.user
|
||||
@@ -1089,3 +1101,127 @@ class AgentOperationsManager:
|
||||
"file_sources": file_sources,
|
||||
"count": len(file_sources),
|
||||
}
|
||||
|
||||
# ==================== Pages (notebooks and reports) ====================
|
||||
|
||||
def _dump_page(self, model, include_rendered: bool = False) -> dict[str, Any]:
|
||||
"""Serialize a page/revision schema, dropping the large rendered form by default.
|
||||
|
||||
content_editor (editable encoded-id markdown) is always kept; content (the
|
||||
embed-expanded render form) is included only when include_rendered is True.
|
||||
"""
|
||||
result = model.model_dump(mode="json")
|
||||
if not include_rendered:
|
||||
result.pop("content", None)
|
||||
return result
|
||||
|
||||
def list_pages(
|
||||
self,
|
||||
history_id: Optional[str] = None,
|
||||
search: Optional[str] = None,
|
||||
limit: int = 100,
|
||||
offset: int = 0,
|
||||
show_published: bool = False,
|
||||
show_shared: bool = False,
|
||||
deleted: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""List pages viewable by the current user.
|
||||
|
||||
When history_id is set, only pages attached to that history (Galaxy
|
||||
Notebooks) are returned. Defaults to the user's own pages; published or
|
||||
shared pages are included only when explicitly requested.
|
||||
"""
|
||||
payload = PageIndexQueryPayload(
|
||||
history_id=history_id,
|
||||
search=search,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
show_own=True,
|
||||
show_published=show_published,
|
||||
show_shared=show_shared,
|
||||
deleted=deleted,
|
||||
)
|
||||
pages, total_matches = self.pages_service.index(self.trans, payload, include_total_count=True)
|
||||
return {
|
||||
"pages": pages.model_dump(mode="json"),
|
||||
"count": len(pages.root),
|
||||
"total_matches": total_matches,
|
||||
}
|
||||
|
||||
def get_page(self, page_id: str, include_rendered: bool = False) -> dict[str, Any]:
|
||||
"""Return a page with its latest-revision content.
|
||||
|
||||
content_editor (editable markdown with encoded-id directives) is always
|
||||
returned. The embed-expanded render form (content) can be large and is
|
||||
included only when include_rendered is True.
|
||||
"""
|
||||
decoded_page_id = self.trans.security.decode_id(page_id)
|
||||
details = self.pages_service.show(self.trans, decoded_page_id)
|
||||
return self._dump_page(details, include_rendered)
|
||||
|
||||
def create_page(
|
||||
self,
|
||||
history_id: Optional[str] = None,
|
||||
title: Optional[str] = None,
|
||||
content: Optional[str] = None,
|
||||
annotation: Optional[str] = None,
|
||||
slug: Optional[str] = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Create a markdown page. Attach it to a history (notebook) by passing history_id.
|
||||
|
||||
Standalone reports (no history_id) require a unique slug and a title.
|
||||
"""
|
||||
payload = CreatePagePayload(
|
||||
history_id=history_id,
|
||||
title=title,
|
||||
content=content,
|
||||
content_format="markdown",
|
||||
annotation=annotation,
|
||||
slug=slug,
|
||||
)
|
||||
details = self.pages_service.create(self.trans, payload)
|
||||
return self._dump_page(details)
|
||||
|
||||
def update_page(
|
||||
self,
|
||||
page_id: str,
|
||||
content: Optional[str] = None,
|
||||
title: Optional[str] = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Update a page. Supplying content creates a new revision tagged edit_source=agent."""
|
||||
decoded_page_id = self.trans.security.decode_id(page_id)
|
||||
payload = UpdatePagePayload(
|
||||
content=content,
|
||||
title=title,
|
||||
edit_source="agent",
|
||||
)
|
||||
details = self.pages_service.update(self.trans, decoded_page_id, payload)
|
||||
return self._dump_page(details)
|
||||
|
||||
def list_page_revisions(self, page_id: str, sort_desc: bool = False) -> dict[str, Any]:
|
||||
"""List the revision history of a page (provenance via edit_source)."""
|
||||
decoded_page_id = self.trans.security.decode_id(page_id)
|
||||
revisions = self.pages_service.list_revisions(self.trans, decoded_page_id, sort_desc=sort_desc)
|
||||
return {
|
||||
"revisions": revisions.model_dump(mode="json"),
|
||||
"count": len(revisions.root),
|
||||
}
|
||||
|
||||
def get_page_revision(self, page_id: str, revision_id: str, include_rendered: bool = False) -> dict[str, Any]:
|
||||
"""Return a single page revision with its content.
|
||||
|
||||
Mirrors get_page: content_editor (editable encoded-id markdown) is always
|
||||
returned; the embed-expanded render form (content) is included only when
|
||||
include_rendered is True.
|
||||
"""
|
||||
decoded_page_id = self.trans.security.decode_id(page_id)
|
||||
decoded_revision_id = self.trans.security.decode_id(revision_id)
|
||||
revision = self.pages_service.show_revision(self.trans, decoded_page_id, decoded_revision_id)
|
||||
return self._dump_page(revision, include_rendered)
|
||||
|
||||
def revert_page_revision(self, page_id: str, revision_id: str) -> dict[str, Any]:
|
||||
"""Roll a page back to an earlier revision (creates a new 'restore' revision)."""
|
||||
decoded_page_id = self.trans.security.decode_id(page_id)
|
||||
decoded_revision_id = self.trans.security.decode_id(revision_id)
|
||||
revision = self.pages_service.revert_revision(self.trans, decoded_page_id, decoded_revision_id)
|
||||
return self._dump_page(revision)
|
||||
|
||||
@@ -363,6 +363,9 @@ class ConditionalDependencies:
|
||||
def check_iiif_fsspec(self):
|
||||
return "iiif" in self.file_sources
|
||||
|
||||
def check_mavedb_fsspec(self):
|
||||
return "mavedb" in self.file_sources
|
||||
|
||||
|
||||
def strip_comment(line):
|
||||
# lifted from https://github.com/tox-dev/tox/commit/3c6b4f204e89852c4b7536b246a66d20be6d39ec
|
||||
|
||||
@@ -36,6 +36,7 @@ huggingface_hub
|
||||
omero-py #type: omero
|
||||
iiif-fsspec # type: iiif
|
||||
fs-irods # type: irods, depends on python-irodsclient
|
||||
mavedb-fsspec>=0.1.0 # type: mavedb
|
||||
|
||||
# Vault backend
|
||||
hvac
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
from typing import (
|
||||
Optional,
|
||||
Union,
|
||||
)
|
||||
|
||||
from galaxy.exceptions import (
|
||||
AuthenticationRequired,
|
||||
MessageException,
|
||||
)
|
||||
from galaxy.files.models import (
|
||||
AnyRemoteEntry,
|
||||
FilesSourceRuntimeContext,
|
||||
)
|
||||
from galaxy.files.sources._defaults import DEFAULT_SCHEME
|
||||
from galaxy.files.sources._fsspec import (
|
||||
CacheOptionsDictType,
|
||||
FsspecBaseFileSourceConfiguration,
|
||||
FsspecBaseFileSourceTemplateConfiguration,
|
||||
FsspecFilesSource,
|
||||
)
|
||||
from galaxy.util.config_templates import TemplateExpansion
|
||||
|
||||
try:
|
||||
from mavedb_fsspec import MaveDBFileSystem
|
||||
from mavedb_fsspec.client import DEFAULT_BASE_URL
|
||||
except ImportError:
|
||||
MaveDBFileSystem = None
|
||||
DEFAULT_BASE_URL = "https://api.mavedb.org/api/v1"
|
||||
|
||||
|
||||
class MaveDBFileSourceTemplateConfiguration(FsspecBaseFileSourceTemplateConfiguration):
|
||||
base_url: Union[str, TemplateExpansion] = DEFAULT_BASE_URL
|
||||
api_key: Union[str, TemplateExpansion, None] = None
|
||||
timeout: Union[float, TemplateExpansion] = 30.0
|
||||
|
||||
|
||||
class MaveDBFileSourceConfiguration(FsspecBaseFileSourceConfiguration):
|
||||
base_url: str = DEFAULT_BASE_URL
|
||||
api_key: Optional[str] = None
|
||||
timeout: float = 30.0
|
||||
|
||||
|
||||
class MaveDBFilesSource(FsspecFilesSource[MaveDBFileSourceTemplateConfiguration, MaveDBFileSourceConfiguration]):
|
||||
plugin_type = "mavedb"
|
||||
required_module = MaveDBFileSystem
|
||||
required_package = "mavedb-fsspec"
|
||||
|
||||
template_config_class = MaveDBFileSourceTemplateConfiguration
|
||||
resolved_config_class = MaveDBFileSourceConfiguration
|
||||
|
||||
def _open_fs(
|
||||
self,
|
||||
context: FilesSourceRuntimeContext[MaveDBFileSourceConfiguration],
|
||||
cache_options: CacheOptionsDictType,
|
||||
) -> "MaveDBFileSystem":
|
||||
if MaveDBFileSystem is None:
|
||||
raise self.required_package_exception
|
||||
|
||||
config = context.config
|
||||
return MaveDBFileSystem(
|
||||
base_url=config.base_url,
|
||||
api_key=config.api_key,
|
||||
timeout=config.timeout,
|
||||
skip_instance_cache=True,
|
||||
**cache_options,
|
||||
)
|
||||
|
||||
def _list(
|
||||
self,
|
||||
context: FilesSourceRuntimeContext[MaveDBFileSourceConfiguration],
|
||||
path="/",
|
||||
recursive=False,
|
||||
write_intent: bool = False,
|
||||
limit: Optional[int] = None,
|
||||
offset: Optional[int] = None,
|
||||
query: Optional[str] = None,
|
||||
sort_by: Optional[str] = None,
|
||||
) -> tuple[list[AnyRemoteEntry], int]:
|
||||
collection = path.strip("/")
|
||||
if recursive or collection not in {"score-sets", "my-score-sets"}:
|
||||
return super()._list(context, path, recursive, write_intent, limit, offset, query, sort_by)
|
||||
|
||||
try:
|
||||
cache_options = self._get_cache_options(context.config)
|
||||
fs = self._open_fs(context, cache_options)
|
||||
try:
|
||||
# Use the MaveDB-specific API for server-side pagination and the total result count.
|
||||
entries, total_count = fs.list_score_sets(
|
||||
collection=collection,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
query=query,
|
||||
)
|
||||
finally:
|
||||
close = getattr(fs, "close", None)
|
||||
if close is not None:
|
||||
close()
|
||||
return [self._info_to_entry(entry, context.config) for entry in entries], total_count
|
||||
except PermissionError as e:
|
||||
raise AuthenticationRequired(
|
||||
f"Permission Denied. Reason: {e}. Please check your credentials in your preferences for {self.label}."
|
||||
)
|
||||
except Exception as e:
|
||||
raise MessageException(f"Problem listing file source path {path}. Reason: {e}") from e
|
||||
|
||||
def _info_to_entry(self, info: dict, config: MaveDBFileSourceConfiguration) -> AnyRemoteEntry:
|
||||
entry = super()._info_to_entry(info, config)
|
||||
display_name = info.get("display_name")
|
||||
if display_name:
|
||||
entry.name = display_name
|
||||
return entry
|
||||
|
||||
def _write_from(
|
||||
self,
|
||||
_target_path: str,
|
||||
_native_path: str,
|
||||
_context: FilesSourceRuntimeContext[MaveDBFileSourceConfiguration],
|
||||
):
|
||||
raise MessageException("MaveDB file sources are read-only and do not support exporting files.")
|
||||
|
||||
def get_scheme(self) -> str:
|
||||
return self.scheme if self.scheme and self.scheme != DEFAULT_SCHEME else "mavedb"
|
||||
|
||||
|
||||
__all__ = ("MaveDBFilesSource",)
|
||||
@@ -0,0 +1,19 @@
|
||||
- id: mavedb
|
||||
version: 0
|
||||
name: MaveDB
|
||||
description: |
|
||||
[MaveDB](https://www.mavedb.org) provides Multiplexed Assays of Variant Effect datasets.
|
||||
This file source exposes score set files through the public MaveDB API.
|
||||
secrets:
|
||||
api_key:
|
||||
label: MaveDB API key
|
||||
optional: true
|
||||
help: |
|
||||
Optional MaveDB API key. Public score set files do not require an API key.
|
||||
A user API key can be generated in the MaveDB settings page for your account
|
||||
and is needed to access private datasets.
|
||||
configuration:
|
||||
type: mavedb
|
||||
base_url: https://api.mavedb.org/api/v1
|
||||
api_key: "{{ secrets.api_key }}"
|
||||
timeout: 30
|
||||
@@ -51,6 +51,7 @@ FileSourceTemplateType = Literal[
|
||||
"dataverse",
|
||||
"huggingface",
|
||||
"iiif",
|
||||
"mavedb",
|
||||
"omero",
|
||||
"ssh",
|
||||
]
|
||||
@@ -440,6 +441,22 @@ class IIIFFileSourceConfiguration(StrictModel):
|
||||
manifest_url: str
|
||||
|
||||
|
||||
class MaveDBFileSourceTemplateConfiguration(StrictModel):
|
||||
type: Literal["mavedb"]
|
||||
base_url: Union[str, TemplateExpansion] = "https://api.mavedb.org/api/v1"
|
||||
api_key: Union[str, TemplateExpansion, None] = None
|
||||
timeout: Union[float, TemplateExpansion] = 30.0
|
||||
template_start: Optional[str] = None
|
||||
template_end: Optional[str] = None
|
||||
|
||||
|
||||
class MaveDBFileSourceConfiguration(StrictModel):
|
||||
type: Literal["mavedb"]
|
||||
base_url: str = "https://api.mavedb.org/api/v1"
|
||||
api_key: Optional[str] = None
|
||||
timeout: float = 30.0
|
||||
|
||||
|
||||
class OmeroFileSourceTemplateConfiguration(StrictModel):
|
||||
type: Literal["omero"]
|
||||
username: Union[str, TemplateExpansion]
|
||||
@@ -480,6 +497,7 @@ FileSourceTemplateConfiguration = Annotated[
|
||||
DataverseFileSourceTemplateConfiguration,
|
||||
HuggingFaceFileSourceTemplateConfiguration,
|
||||
IIIFFileSourceTemplateConfiguration,
|
||||
MaveDBFileSourceTemplateConfiguration,
|
||||
OmeroFileSourceTemplateConfiguration,
|
||||
SshFileSourceTemplateConfiguration,
|
||||
],
|
||||
@@ -506,6 +524,7 @@ FileSourceConfiguration = Annotated[
|
||||
DataverseFileSourceConfiguration,
|
||||
HuggingFaceFileSourceConfiguration,
|
||||
IIIFFileSourceConfiguration,
|
||||
MaveDBFileSourceConfiguration,
|
||||
OmeroFileSourceConfiguration,
|
||||
SshFileSourceConfiguration,
|
||||
],
|
||||
@@ -590,6 +609,7 @@ TypesToConfigurationClasses: dict[FileSourceTemplateType, type[FileSourceConfigu
|
||||
"dataverse": DataverseFileSourceConfiguration,
|
||||
"huggingface": HuggingFaceFileSourceConfiguration,
|
||||
"iiif": IIIFFileSourceConfiguration,
|
||||
"mavedb": MaveDBFileSourceConfiguration,
|
||||
"omero": OmeroFileSourceConfiguration,
|
||||
"ssh": SshFileSourceConfiguration,
|
||||
}
|
||||
|
||||
@@ -73,7 +73,9 @@ SYNTHETIC_TOOL_IDS: tuple[str, ...] = ("__DATA_FETCH__",)
|
||||
|
||||
def _summary_dict(summary) -> Optional[dict[str, int]]:
|
||||
"""Lift JobStateSummary (a NamedTuple) to a dict, or None."""
|
||||
return None if summary is None else summary._asdict()
|
||||
if summary is None or summary.all_jobs == 0:
|
||||
return None
|
||||
return summary._asdict()
|
||||
|
||||
|
||||
class HistoryGraphManager:
|
||||
@@ -601,8 +603,6 @@ class HistoryGraphBuilder:
|
||||
]
|
||||
|
||||
def _tr_nodes(self, tr_map: dict[int, Optional[str]]) -> list[GraphNode]:
|
||||
if not tr_map:
|
||||
return []
|
||||
return [self._node("tool_request", tr_id, tool_id=tool_id) for tr_id, tool_id in tr_map.items()]
|
||||
|
||||
def _resolve_tool_names(self, nodes: list[GraphNode]) -> None:
|
||||
|
||||
@@ -4376,6 +4376,7 @@ class PageRevisionSummary(Model):
|
||||
class PageRevisionDetails(PageRevisionSummary):
|
||||
title: Optional[str] = None
|
||||
content: Optional[str] = None
|
||||
content_editor: Optional[str] = ContentEditorField
|
||||
content_format: Optional[PageContentFormat] = None
|
||||
|
||||
|
||||
|
||||
@@ -1180,6 +1180,238 @@ def get_mcp_app(gx_app):
|
||||
ops_manager = get_operations_manager(api_key, ctx)
|
||||
return ops_manager.import_workflow_from_iwc(trs_id)
|
||||
|
||||
# ==================== Pages (notebooks and reports) ====================
|
||||
|
||||
@mcp.tool()
|
||||
def list_pages(
|
||||
api_key: str,
|
||||
ctx: MCPContext,
|
||||
history_id: str | None = None,
|
||||
search: str | None = None,
|
||||
limit: int = 100,
|
||||
offset: int = 0,
|
||||
show_published: bool = False,
|
||||
show_shared: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""List Galaxy pages (markdown documents) viewable by the user.
|
||||
|
||||
A page attached to a history is a "Galaxy Notebook"; a standalone page is
|
||||
a "Report". Pass history_id to list only that history's notebooks.
|
||||
|
||||
Args:
|
||||
history_id: Encoded history id. When set, returns only notebooks
|
||||
attached to that history.
|
||||
search: Freetext filter over title/content.
|
||||
limit: Max pages to return (default 100).
|
||||
offset: Pagination offset.
|
||||
show_published: Also include pages published by other users (default False).
|
||||
show_shared: Also include pages shared with the user (default False).
|
||||
|
||||
Returns:
|
||||
Dict with `pages` (summaries incl. encoded id, title, history_id,
|
||||
latest_revision_id), `count`, and `total_matches`.
|
||||
|
||||
NEXT STEPS:
|
||||
- Read one: get_page(page_id)
|
||||
- Create a notebook: create_page(history_id=...)
|
||||
"""
|
||||
with _mcp_error_handler("list_pages"):
|
||||
ops_manager = get_operations_manager(api_key, ctx)
|
||||
return ops_manager.list_pages(
|
||||
history_id=history_id,
|
||||
search=search,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
show_published=show_published,
|
||||
show_shared=show_shared,
|
||||
)
|
||||
|
||||
@mcp.tool()
|
||||
def get_page(
|
||||
page_id: str,
|
||||
api_key: str,
|
||||
ctx: MCPContext,
|
||||
include_rendered: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""Get a page and the content of its latest revision.
|
||||
|
||||
Returns `content_editor`: the editable Galaxy-flavored markdown, with
|
||||
ENCODED ids in directives (e.g. `history_dataset_id=f2db41e1fa331b3e`).
|
||||
This is the form to edit and pass back to update_page.
|
||||
|
||||
Args:
|
||||
page_id: Encoded id of the page (from list_pages / create_page).
|
||||
include_rendered: When True, also return `content` -- the
|
||||
embed-expanded render form (inlined dataset previews). Can be
|
||||
large; omit unless you need the rendered output.
|
||||
|
||||
Returns:
|
||||
Page details including `content_editor`, metadata, and `edit_source`
|
||||
of the latest revision.
|
||||
|
||||
NEXT STEPS:
|
||||
- Edit it: update_page(page_id, content=...)
|
||||
- See history: list_page_revisions(page_id)
|
||||
"""
|
||||
with _mcp_error_handler("get_page"):
|
||||
ops_manager = get_operations_manager(api_key, ctx)
|
||||
return ops_manager.get_page(page_id, include_rendered=include_rendered)
|
||||
|
||||
@mcp.tool()
|
||||
def create_page(
|
||||
api_key: str,
|
||||
ctx: MCPContext,
|
||||
history_id: str | None = None,
|
||||
title: str | None = None,
|
||||
content: str | None = None,
|
||||
annotation: str | None = None,
|
||||
slug: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Create a markdown page (Galaxy Notebook or Report).
|
||||
|
||||
Pass history_id to create a history-attached notebook (title auto-fills
|
||||
from the history if omitted). Omit history_id to create a standalone
|
||||
report -- reports REQUIRE both a title and a unique slug
|
||||
(lowercase/digits/hyphens).
|
||||
|
||||
Content is Galaxy-flavored markdown. To embed a dataset, use a directive
|
||||
with the ENCODED dataset id, e.g.
|
||||
`history_dataset_display(history_dataset_id=f2db41e1fa331b3e)` or
|
||||
`history_dataset_collection_display(history_dataset_collection_id=...)`.
|
||||
Get encoded ids from get_history_contents / get_dataset_details.
|
||||
|
||||
Args:
|
||||
history_id: Encoded history id to attach the page to (notebook).
|
||||
title: Page title.
|
||||
content: Initial markdown content.
|
||||
annotation: Optional annotation attached to the page.
|
||||
slug: URL slug (required for standalone reports).
|
||||
|
||||
Returns:
|
||||
Created page details including encoded id and content_editor.
|
||||
|
||||
NEXT STEPS:
|
||||
- Edit it: update_page(page_id, content=...)
|
||||
"""
|
||||
with _mcp_error_handler("create_page"):
|
||||
ops_manager = get_operations_manager(api_key, ctx)
|
||||
return ops_manager.create_page(
|
||||
history_id=history_id,
|
||||
title=title,
|
||||
content=content,
|
||||
annotation=annotation,
|
||||
slug=slug,
|
||||
)
|
||||
|
||||
@mcp.tool()
|
||||
def update_page(
|
||||
page_id: str,
|
||||
api_key: str,
|
||||
ctx: MCPContext,
|
||||
content: str | None = None,
|
||||
title: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Update a page, creating a new revision when content changes.
|
||||
|
||||
Content is Galaxy-flavored markdown using ENCODED ids in directives
|
||||
(e.g. `history_dataset_id=f2db41e1fa331b3e`) -- never raw integer ids or
|
||||
HIDs. Get encoded ids from get_history_contents / get_dataset_details.
|
||||
Edits made through this tool are recorded with edit_source="agent".
|
||||
|
||||
Args:
|
||||
page_id: Encoded id of the page.
|
||||
content: New markdown content. Omit to leave content unchanged.
|
||||
title: New title. Omit to leave unchanged.
|
||||
|
||||
Returns:
|
||||
Updated page details including content_editor.
|
||||
|
||||
NEXT STEPS:
|
||||
- Inspect revisions: list_page_revisions(page_id)
|
||||
- Roll back: revert_page_revision(page_id, revision_id)
|
||||
"""
|
||||
with _mcp_error_handler("update_page"):
|
||||
ops_manager = get_operations_manager(api_key, ctx)
|
||||
return ops_manager.update_page(page_id, content=content, title=title)
|
||||
|
||||
@mcp.tool()
|
||||
def list_page_revisions(
|
||||
page_id: str,
|
||||
api_key: str,
|
||||
ctx: MCPContext,
|
||||
sort_desc: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""List the revision history of a page.
|
||||
|
||||
Each revision carries an `edit_source` ("user", "agent", or "restore")
|
||||
recording who made the edit.
|
||||
|
||||
Args:
|
||||
page_id: Encoded id of the page.
|
||||
sort_desc: Newest-first when True (default oldest-first).
|
||||
|
||||
Returns:
|
||||
Dict with `revisions` (encoded id, page_id, edit_source, timestamps)
|
||||
and `count`.
|
||||
|
||||
NEXT STEPS:
|
||||
- Read a revision: get_page_revision(page_id, revision_id)
|
||||
- Roll back: revert_page_revision(page_id, revision_id)
|
||||
"""
|
||||
with _mcp_error_handler("list_page_revisions"):
|
||||
ops_manager = get_operations_manager(api_key, ctx)
|
||||
return ops_manager.list_page_revisions(page_id, sort_desc=sort_desc)
|
||||
|
||||
@mcp.tool()
|
||||
def get_page_revision(
|
||||
page_id: str,
|
||||
revision_id: str,
|
||||
api_key: str,
|
||||
ctx: MCPContext,
|
||||
include_rendered: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""Get the content of a single page revision.
|
||||
|
||||
Mirrors get_page: returns `content_editor` (the editable Galaxy-flavored
|
||||
markdown with ENCODED ids in directives) -- the form to compare against
|
||||
get_page or pass back to update_page.
|
||||
|
||||
Args:
|
||||
page_id: Encoded id of the page.
|
||||
revision_id: Encoded id of the revision (from list_page_revisions).
|
||||
include_rendered: When True, also return `content` -- the
|
||||
embed-expanded render form. Can be large; omit unless needed.
|
||||
|
||||
Returns:
|
||||
Revision details including `content_editor`, `edit_source`, and timestamps.
|
||||
"""
|
||||
with _mcp_error_handler("get_page_revision"):
|
||||
ops_manager = get_operations_manager(api_key, ctx)
|
||||
return ops_manager.get_page_revision(page_id, revision_id, include_rendered=include_rendered)
|
||||
|
||||
@mcp.tool()
|
||||
def revert_page_revision(
|
||||
page_id: str,
|
||||
revision_id: str,
|
||||
api_key: str,
|
||||
ctx: MCPContext,
|
||||
) -> dict[str, Any]:
|
||||
"""Roll a page back to an earlier revision.
|
||||
|
||||
Creates a NEW revision from the target revision's content, recorded with
|
||||
edit_source="restore" (the history is append-only -- nothing is deleted).
|
||||
|
||||
Args:
|
||||
page_id: Encoded id of the page.
|
||||
revision_id: Encoded id of the revision to restore.
|
||||
|
||||
Returns:
|
||||
The new restored revision's details.
|
||||
"""
|
||||
with _mcp_error_handler("revert_page_revision"):
|
||||
ops_manager = get_operations_manager(api_key, ctx)
|
||||
return ops_manager.revert_page_revision(page_id, revision_id)
|
||||
|
||||
mcp_app = mcp.http_app(path="/")
|
||||
mcp_app.state.mcp_server = mcp
|
||||
|
||||
|
||||
@@ -349,6 +349,13 @@ class TestMCPServerSmoke(IntegrationTestCase):
|
||||
"search_iwc_workflows",
|
||||
"get_iwc_workflow_details",
|
||||
"import_workflow_from_iwc",
|
||||
"list_pages",
|
||||
"get_page",
|
||||
"create_page",
|
||||
"update_page",
|
||||
"list_page_revisions",
|
||||
"get_page_revision",
|
||||
"revert_page_revision",
|
||||
}
|
||||
assert expected.issubset(tool_names), f"Missing tools: {expected - tool_names}"
|
||||
|
||||
@@ -563,3 +570,87 @@ class TestMCPServerSmoke(IntegrationTestCase):
|
||||
data = result.data
|
||||
assert "id" in data
|
||||
assert data["trsID"] == trs_id
|
||||
|
||||
def test_mcp_page_lifecycle(self):
|
||||
"""create_page -> get_page -> update_page -> list_page_revisions round-trip."""
|
||||
mcp_server = self._get_mcp_server()
|
||||
api_key = self._get_api_key()
|
||||
populator = DatasetPopulator(self._get_interactor(api_key=api_key))
|
||||
history_id = populator.new_history()
|
||||
|
||||
async def _flow():
|
||||
async with Client(mcp_server) as client:
|
||||
created = await client.call_tool(
|
||||
"create_page",
|
||||
{"api_key": api_key, "history_id": history_id, "title": "MCP Notebook", "content": "# Intro\n"},
|
||||
)
|
||||
page_id = created.data["id"]
|
||||
got = await client.call_tool("get_page", {"api_key": api_key, "page_id": page_id})
|
||||
await client.call_tool(
|
||||
"update_page",
|
||||
{"api_key": api_key, "page_id": page_id, "content": "# Intro\n\n## Methods\n"},
|
||||
)
|
||||
revisions = await client.call_tool("list_page_revisions", {"api_key": api_key, "page_id": page_id})
|
||||
listed = await client.call_tool("list_pages", {"api_key": api_key, "history_id": history_id})
|
||||
return created, got, revisions, listed
|
||||
|
||||
created, got, revisions, listed = self._run_async(_flow())
|
||||
assert not created.is_error, created
|
||||
assert created.data["history_id"] == history_id
|
||||
# get_page returns the editable content and omits the rendered form by default
|
||||
assert "content_editor" in got.data
|
||||
assert "content" not in got.data
|
||||
# update_page created a second revision tagged with edit_source="agent"
|
||||
assert revisions.data["count"] == 2
|
||||
assert revisions.data["revisions"][-1]["edit_source"] == "agent"
|
||||
# the notebook shows up when listing pages for its history
|
||||
assert created.data["id"] in {p["id"] for p in listed.data["pages"]}
|
||||
|
||||
def test_mcp_page_directive_and_revert(self):
|
||||
"""Dataset directives survive in encoded-id space; revert restores prior content."""
|
||||
mcp_server = self._get_mcp_server()
|
||||
api_key = self._get_api_key()
|
||||
populator = DatasetPopulator(self._get_interactor(api_key=api_key))
|
||||
history_id = populator.new_history()
|
||||
hda_id = populator.new_dataset(history_id)["id"]
|
||||
directive = f"# Analysis\n\n```galaxy\nhistory_dataset_display(history_dataset_id={hda_id})\n```\n"
|
||||
|
||||
async def _flow():
|
||||
async with Client(mcp_server) as client:
|
||||
created = await client.call_tool(
|
||||
"create_page",
|
||||
{"api_key": api_key, "history_id": history_id, "title": "Directive NB", "content": directive},
|
||||
)
|
||||
page_id = created.data["id"]
|
||||
editor = await client.call_tool("get_page", {"api_key": api_key, "page_id": page_id})
|
||||
rendered = await client.call_tool(
|
||||
"get_page", {"api_key": api_key, "page_id": page_id, "include_rendered": True}
|
||||
)
|
||||
await client.call_tool(
|
||||
"update_page", {"api_key": api_key, "page_id": page_id, "content": "# Replaced\n"}
|
||||
)
|
||||
revisions = await client.call_tool("list_page_revisions", {"api_key": api_key, "page_id": page_id})
|
||||
first_rev_id = revisions.data["revisions"][0]["id"]
|
||||
old_rev = await client.call_tool(
|
||||
"get_page_revision",
|
||||
{"api_key": api_key, "page_id": page_id, "revision_id": first_rev_id},
|
||||
)
|
||||
reverted = await client.call_tool(
|
||||
"revert_page_revision",
|
||||
{"api_key": api_key, "page_id": page_id, "revision_id": first_rev_id},
|
||||
)
|
||||
return editor, rendered, old_rev, reverted
|
||||
|
||||
editor, rendered, old_rev, reverted = self._run_async(_flow())
|
||||
# content_editor keeps the directive with the ENCODED dataset id (encoded-id space)
|
||||
assert hda_id in editor.data["content_editor"]
|
||||
assert "content" not in editor.data
|
||||
# include_rendered exposes the expanded render form
|
||||
assert "content" in rendered.data
|
||||
# get_page_revision mirrors get_page: editable content_editor by default, rendered omitted
|
||||
assert hda_id in old_rev.data["content_editor"]
|
||||
assert "content" not in old_rev.data
|
||||
# revert appends a new "restore" revision carrying the original directive content
|
||||
assert reverted.data["edit_source"] == "restore"
|
||||
assert hda_id in reverted.data["content_editor"]
|
||||
assert "content" not in reverted.data
|
||||
|
||||
@@ -3,6 +3,7 @@ from unittest import mock
|
||||
import pytest
|
||||
|
||||
from galaxy.agents.operations import AgentOperationsManager
|
||||
from galaxy.schema.fields import Security
|
||||
from .base import BaseTestCase
|
||||
|
||||
|
||||
@@ -419,3 +420,190 @@ class TestAgentOperationsManagerWithMockedServices(BaseTestCase):
|
||||
|
||||
with pytest.raises(ValueError, match="User must be authenticated"):
|
||||
agent_ops.list_user_file_sources()
|
||||
|
||||
|
||||
class TestAgentOperationsManagerPages(BaseTestCase):
|
||||
"""Page (notebook/report) operations against a mocked PagesService."""
|
||||
|
||||
def set_up_managers(self):
|
||||
super().set_up_managers()
|
||||
# DecodedDatabaseIdField on page payloads decodes via the module-global
|
||||
# Security.security; point it at the same helper trans uses to encode.
|
||||
Security.security = self.trans.security
|
||||
self.agent_ops = AgentOperationsManager(app=self.app, trans=self.trans)
|
||||
|
||||
def _mock_pages_service(self, mock_service):
|
||||
return mock.patch.object(
|
||||
type(self.agent_ops), "pages_service", new_callable=lambda: property(lambda self: mock_service)
|
||||
)
|
||||
|
||||
def test_list_pages_defaults_to_own_only(self):
|
||||
pages = mock.MagicMock()
|
||||
pages.model_dump.return_value = []
|
||||
pages.root = []
|
||||
mock_service = mock.MagicMock()
|
||||
mock_service.index.return_value = (pages, 0)
|
||||
|
||||
with self._mock_pages_service(mock_service):
|
||||
result = self.agent_ops.list_pages()
|
||||
|
||||
payload = mock_service.index.call_args.args[1]
|
||||
assert payload.show_own is True
|
||||
assert payload.show_published is False
|
||||
assert payload.show_shared is False
|
||||
assert payload.history_id is None
|
||||
assert result["count"] == 0
|
||||
assert result["total_matches"] == 0
|
||||
|
||||
def test_list_pages_history_filter(self):
|
||||
encoded_history_id = self.trans.security.encode_id(42)
|
||||
pages = mock.MagicMock()
|
||||
pages.model_dump.return_value = []
|
||||
pages.root = []
|
||||
mock_service = mock.MagicMock()
|
||||
mock_service.index.return_value = (pages, 5)
|
||||
|
||||
with self._mock_pages_service(mock_service):
|
||||
result = self.agent_ops.list_pages(history_id=encoded_history_id)
|
||||
|
||||
payload = mock_service.index.call_args.args[1]
|
||||
assert payload.history_id == 42
|
||||
assert result["total_matches"] == 5
|
||||
|
||||
def test_get_page_drops_rendered_by_default(self):
|
||||
details = mock.MagicMock()
|
||||
details.model_dump.return_value = {"id": "p1", "content_editor": "# Notes", "content": "<rendered>"}
|
||||
mock_service = mock.MagicMock()
|
||||
mock_service.show.return_value = details
|
||||
|
||||
with (
|
||||
self._mock_pages_service(mock_service),
|
||||
mock.patch.object(self.trans.security, "decode_id", return_value=7),
|
||||
):
|
||||
result = self.agent_ops.get_page("encoded_page")
|
||||
|
||||
assert result["content_editor"] == "# Notes"
|
||||
assert "content" not in result
|
||||
mock_service.show.assert_called_once_with(self.trans, 7)
|
||||
|
||||
def test_get_page_includes_rendered_when_requested(self):
|
||||
details = mock.MagicMock()
|
||||
details.model_dump.return_value = {"id": "p1", "content_editor": "# Notes", "content": "<rendered>"}
|
||||
mock_service = mock.MagicMock()
|
||||
mock_service.show.return_value = details
|
||||
|
||||
with (
|
||||
self._mock_pages_service(mock_service),
|
||||
mock.patch.object(self.trans.security, "decode_id", return_value=7),
|
||||
):
|
||||
result = self.agent_ops.get_page("encoded_page", include_rendered=True)
|
||||
|
||||
assert result["content"] == "<rendered>"
|
||||
|
||||
def test_create_page_sets_markdown_format(self):
|
||||
details = mock.MagicMock()
|
||||
details.model_dump.return_value = {"id": "p1"}
|
||||
mock_service = mock.MagicMock()
|
||||
mock_service.create.return_value = details
|
||||
|
||||
with self._mock_pages_service(mock_service):
|
||||
self.agent_ops.create_page(title="Report", content="# Hi", slug="my-report")
|
||||
|
||||
payload = mock_service.create.call_args.args[1]
|
||||
assert payload.content_format == "markdown"
|
||||
assert payload.title == "Report"
|
||||
assert payload.slug == "my-report"
|
||||
assert payload.history_id is None
|
||||
|
||||
def test_create_page_with_history_decodes_id(self):
|
||||
encoded_history_id = self.trans.security.encode_id(99)
|
||||
details = mock.MagicMock()
|
||||
details.model_dump.return_value = {"id": "p1"}
|
||||
mock_service = mock.MagicMock()
|
||||
mock_service.create.return_value = details
|
||||
|
||||
with self._mock_pages_service(mock_service):
|
||||
self.agent_ops.create_page(history_id=encoded_history_id, content="# Hi")
|
||||
|
||||
payload = mock_service.create.call_args.args[1]
|
||||
assert payload.history_id == 99
|
||||
assert payload.content_format == "markdown"
|
||||
|
||||
def test_update_page_sets_edit_source_agent(self):
|
||||
details = mock.MagicMock()
|
||||
details.model_dump.return_value = {"id": "p1"}
|
||||
mock_service = mock.MagicMock()
|
||||
mock_service.update.return_value = details
|
||||
|
||||
with (
|
||||
self._mock_pages_service(mock_service),
|
||||
mock.patch.object(self.trans.security, "decode_id", return_value=7),
|
||||
):
|
||||
self.agent_ops.update_page("encoded_page", content="# New")
|
||||
|
||||
call = mock_service.update.call_args
|
||||
assert call.args[1] == 7
|
||||
payload = call.args[2]
|
||||
assert payload.edit_source == "agent"
|
||||
assert payload.content == "# New"
|
||||
|
||||
def test_list_page_revisions(self):
|
||||
revisions = mock.MagicMock()
|
||||
revisions.model_dump.return_value = [{"id": "r1"}]
|
||||
revisions.root = [{"id": "r1"}]
|
||||
mock_service = mock.MagicMock()
|
||||
mock_service.list_revisions.return_value = revisions
|
||||
|
||||
with (
|
||||
self._mock_pages_service(mock_service),
|
||||
mock.patch.object(self.trans.security, "decode_id", return_value=7),
|
||||
):
|
||||
result = self.agent_ops.list_page_revisions("encoded_page")
|
||||
|
||||
mock_service.list_revisions.assert_called_once_with(self.trans, 7, sort_desc=False)
|
||||
assert result["count"] == 1
|
||||
|
||||
def test_get_page_revision_drops_rendered_by_default(self):
|
||||
revision = mock.MagicMock()
|
||||
revision.model_dump.return_value = {"id": "r1", "content_editor": "# Notes", "content": "<rendered>"}
|
||||
mock_service = mock.MagicMock()
|
||||
mock_service.show_revision.return_value = revision
|
||||
|
||||
with (
|
||||
self._mock_pages_service(mock_service),
|
||||
mock.patch.object(self.trans.security, "decode_id", side_effect=[7, 3]),
|
||||
):
|
||||
result = self.agent_ops.get_page_revision("encoded_page", "encoded_rev")
|
||||
|
||||
mock_service.show_revision.assert_called_once_with(self.trans, 7, 3)
|
||||
assert result["content_editor"] == "# Notes"
|
||||
assert "content" not in result
|
||||
|
||||
def test_get_page_revision_includes_rendered_when_requested(self):
|
||||
revision = mock.MagicMock()
|
||||
revision.model_dump.return_value = {"id": "r1", "content_editor": "# Notes", "content": "<rendered>"}
|
||||
mock_service = mock.MagicMock()
|
||||
mock_service.show_revision.return_value = revision
|
||||
|
||||
with (
|
||||
self._mock_pages_service(mock_service),
|
||||
mock.patch.object(self.trans.security, "decode_id", side_effect=[7, 3]),
|
||||
):
|
||||
result = self.agent_ops.get_page_revision("encoded_page", "encoded_rev", include_rendered=True)
|
||||
|
||||
assert result["content"] == "<rendered>"
|
||||
|
||||
def test_revert_page_revision_returns_restore(self):
|
||||
revision = mock.MagicMock()
|
||||
revision.model_dump.return_value = {"id": "r2", "edit_source": "restore"}
|
||||
mock_service = mock.MagicMock()
|
||||
mock_service.revert_revision.return_value = revision
|
||||
|
||||
with (
|
||||
self._mock_pages_service(mock_service),
|
||||
mock.patch.object(self.trans.security, "decode_id", side_effect=[7, 3]),
|
||||
):
|
||||
result = self.agent_ops.revert_page_revision("encoded_page", "encoded_rev")
|
||||
|
||||
mock_service.revert_revision.assert_called_once_with(self.trans, 7, 3)
|
||||
assert result["edit_source"] == "restore"
|
||||
|
||||
Reference in New Issue
Block a user