Implement tool request form and notification system#22259
Implement tool request form and notification system#22259arash77 wants to merge 29 commits intogalaxyproject:devfrom
Conversation
c0b39cc to
db2f14a
Compare
davelopez
left a comment
There was a problem hiding this comment.
Looking pretty solid already!
Some comments for potential improvements below.
| async function mountForm(show = true): Promise<Wrapper<Vue>> { | ||
| // Suppress FormBoolean null-value prop warnings — condaAvailable and testDataAvailable | ||
| // are intentionally null until the user interacts with them. | ||
| vi.spyOn(console, "error").mockImplementation(() => {}); |
There was a problem hiding this comment.
We should not silence all console errors; there are some helpers to do that in client/tests/vitest/helpers.js
There was a problem hiding this comment.
Switched to suppressExpectedErrorMessages from @tests/vitest/helpers (ToolRequestForm.test.ts:4,23), scoped to the expected "Invalid prop: type check failed for prop" pattern only.
| <h6 v-localize class="font-weight-bold mb-2">Tool Information</h6> | ||
|
|
||
| <BFormGroup label="Tool Name *" label-for="tool-request-name"> | ||
| <BFormInput |
There was a problem hiding this comment.
Instead of bootstrap inputs, you can use Galaxy's FormElement. It should give you more consistent forms UI with the rest of Galaxy.
There was a problem hiding this comment.
Replaced bootstrap inputs with <FormElement> in f4db42d (ToolRequestForm.vue:9,133-177). Only BAlert for success/error banners remains.
| `, | ||
| new_shared_item: | ||
| "You will receive these notifications when someone shares an item with you i.e. a history, workflow, visualization, etc.", | ||
| tool_request: |
There was a problem hiding this comment.
Hmmm, interesting... this will not make sense for normal users, only for admins. And if admins opt out of this kind of notification, then it will defeat the purpose... so probably better drop the setting from the UI.
There was a problem hiding this comment.
Cleaned this up in b4fe427: tool_request is filtered out of user-facing categories, and the stale tool_request entry was removed from categoryDescriptionMap.
| <template v-if="props.notification.content.tool_url"> | ||
| <dt>URL</dt> | ||
| <dd> | ||
| <BLink :href="props.notification.content.tool_url" target="_blank"> |
There was a problem hiding this comment.
I will not render this as a link to avoid accidentally clicking it in case of a "malicious" user trying to do some kind of Admin-targeted URL injection.
| try { | ||
| await submitToolRequest({ | ||
| tool_name: toolName.value.trim(), | ||
| tool_url: toolUrl.value.trim() || undefined, |
There was a problem hiding this comment.
Not sure if you want to do any more validation for the URL here; there should be some utils for that. Like ignoring non-https links, etc.
There was a problem hiding this comment.
Updated in b4fe427: ToolRequestForm.vue now uses the shared isValidNetworkUrl utility from @/utils/url, composed with the local https:// requirement. I kept the HTTPS-only policy here because url.ts’s broader validateUrl() accepts upload-flow schemes like ftp:///gxfiles://.
| desc: | | ||
| Enable the integration of the Galaxy Help Forum in the tool panel. This requires the help_forum_api_url to be set. | ||
|
|
||
| enable_tool_request_form: |
There was a problem hiding this comment.
When you add a new config option, you should run make config-rebuild.
There was a problem hiding this comment.
Done in d47de6d (galaxy.yml.sample:3225 now contains #enable_tool_request_form: false).
|
|
||
| message = "message" | ||
| new_shared_item = "new_shared_item" | ||
| tool_request = "tool_request" |
There was a problem hiding this comment.
This new category does not have email "wiring". Have a look at email_templates_by_category and lib/galaxy/config/templates/mail/notifications/
There was a problem hiding this comment.
Wired in bc38745. lib/galaxy/managers/notification.py:860 maps PersonalNotificationCategory.tool_request → ToolRequestEmailNotificationTemplateBuilder in email_templates_by_category. Templates added: lib/galaxy/config/templates/mail/notifications/tool_request-email.{html,txt}.
| requester_name: requesterName.value.trim(), | ||
| requester_email: requesterEmail.value.trim() || undefined, |
There was a problem hiding this comment.
Thinking about this, probably the user details should be filled out by the server to avoid any spoofing attempts.
mvdbeek
left a comment
There was a problem hiding this comment.
I like most of it, but it'd be awesome if you can use the notifications API instead of a bespoke API for this.
| patch?: never; | ||
| trace?: never; | ||
| }; | ||
| "/api/tool_request_form": { |
There was a problem hiding this comment.
Can we please use an existing route (or make a generic one) for that ? We don't want a new endpoint for every type of message we could add.
There was a problem hiding this comment.
Done in e18329a. Submission now POST /api/notifications (client/src/api/notifications.ts:74); dedicated tool-request endpoint removed.
There was a problem hiding this comment.
Currently, only admins can POST new notifications (to avoid any potential spam), but maybe you can add a filter to allow certain "categories" of notifications, starting with this one :)
There was a problem hiding this comment.
This has already been done: non-admin submissions now go through POST /api/notifications and are limited by a server-side allow-list, currently only tool_request.
There was a problem hiding this comment.
True! Sorry for the noise!
| */ | ||
| ToolRequestFormData: { | ||
| /** | ||
| * Conda available |
There was a problem hiding this comment.
That doesn't seem like something a user would know, and even if they did we can't trust that so it's just more work, and even if there is a package it doesn't help if it's in some random channel.
There was a problem hiding this comment.
Removed in 3f22971f9. No conda_available field anywhere in the current schema; test asserts absent (ToolRequestForm.test.ts:117).
| * Requester affiliation | ||
| * @description The affiliation/lab of the requester. | ||
| */ | ||
| requester_affiliation?: string | null; |
There was a problem hiding this comment.
I'd just add an additional remarks field here. If they use OIDC or institutional email we can get that from their user profile.
There was a problem hiding this comment.
Replaced with additional_remarks (notifications.py:147, notifications.ts:41,67). requester_email is derived server-side from the authenticated user's profile.
| * Test data available | ||
| * @description Whether test data for this tool is available. | ||
| */ | ||
| test_data_available?: boolean | null; |
There was a problem hiding this comment.
Let's not do that, same reason as the conda check
There was a problem hiding this comment.
Removed in 3f22971f9. No test_data_available field; test asserts absent (ToolRequestForm.test.ts:118).
| * Tool name | ||
| * @description The name of the requested tool. | ||
| */ | ||
| tool_name: string; |
There was a problem hiding this comment.
This is a string, but tool ids is an array, so that doesn't line up
There was a problem hiding this comment.
Now tool_names: list[str] with min_length=1 (notifications.py:122-124); form sends array [toolName.value.trim()] (ToolRequestForm.vue:86); TypeScript type tool_names: string[] (notifications.ts:34,61).
| * Workflow name | ||
| * @description Name of the workflow requiring these tools, if applicable. | ||
| */ | ||
| workflow_name?: string | null; |
There was a problem hiding this comment.
A workflow id that we should have in the context is probably more useful ?
There was a problem hiding this comment.
Done in 252a8d2. WorkflowMissingToolsRequest.vue:13-16 takes workflowId as a prop and submits it as workflow_id at line 48 (schema field notifications.py:144).
| </BAlert> | ||
|
|
||
| <BAlert v-if="submitted || alreadyRequested" variant="success" show> | ||
| Installation request sent — admins have been notified and will review it shortly. |
There was a problem hiding this comment.
Don't the notifications have an ID ? Seems like we could link to the actual notification message
There was a problem hiding this comment.
submitUserNotification now returns the encoded notification id (notifications.ts:73-93); the component captures it (WorkflowMissingToolsRequest.vue:46,52) and renders a link: <BLink :href="/user/notifications#notification-card-${submittedNotificationId}">view your request</BLink> (line 70). The anchor target is the id on each card (NotificationCard.vue:157).
| <BAlert v-if="workflowError" variant="danger" show> | ||
| <h2 class="h-text">Workflow cannot be executed. Please resolve the following issue:</h2> | ||
| {{ workflowError }} | ||
| <WorkflowMissingToolsRequest :missing-tool-ids="missingToolIds" :workflow-name="workflowName" /> |
There was a problem hiding this comment.
Doesn't that need to be guarded by whether the feature is enabled ?
There was a problem hiding this comment.
Yes — <WorkflowMissingToolsRequest v-if="config?.enable_tool_request_form" ...> (WorkflowRun.vue:242-243). The component itself double-guards (WorkflowMissingToolsRequest.vue:26-32): isConfigLoaded && config.enable_tool_request_form && !userStore.isAnonymous.
…ng and add tests for dialog visibility
…l watch - ToolBox.vue: change v-model:show to :show.sync (Vue 2 syntax), remove template ref since modal is controlled reactively via show prop - ToolRequestForm.vue: remove redundant watch/ref/expose since GModal already handles show/hide via its own watchImmediate on the show prop - ToolRequestForm.test.ts: replace dialog.open DOM assertion with GModal props check for environment-agnostic testing
…olean fields FormBoolean requires Boolean|String prop and warns on null, causing ToolPanel and ToolBoxSearch tests to fail via vitest-fail-on-console. Use BFormSelect with Not specified/Yes/No options instead, which accepts null as a valid model value for the tri-state scenario.
- Move inline `import datetime` to top-level imports in tool_request_form service - Fix misleading test name and docstring: returns_422 → returns_400 - DRY up form reset in ToolRequestForm.vue: use resetForm() in submit success handler
Previously the button was always visible below the search controls. Now it appears contextually inside the "No results found" empty state, grouping the call-to-action with the feedback that triggered it. - Move button into the `queryFinished && !hasResults` block in ToolBox.vue - Button remains hidden for anonymous users, in workflow mode, and when `enable_tool_request_form` config is disabled - Fix `isConfigLoaded` in the config mock to return a `ref(true)` rather than a plain boolean, matching the real composable - Add 6 tests covering button visibility: with/without results, config disabled, anonymous user, workflow mode, and modal open-on-click
When a workflow run fails because required tools are not installed, Galaxy now detects which tools are missing and surfaces a one-click 'Request Installation' button so users can notify admins without leaving the workflow run page. Backend changes: - workflows.py: pass missing_tool_ids as extra_error_info on the MessageException so the list is serialised into the JSON error response - notifications.py: add optional tool_ids and workflow_name fields to ToolRequestNotificationContent so admin notifications include the workflow context - tool_request_form.py: mirror the same two fields on ToolRequestFormData and wire them through to the notification content - test_tool_request_form.py: add integration test that submits a request with tool_ids + workflow_name and verifies both appear in the admin notification Frontend changes (client/src/components/Workflow/Run/): - services.js: add WorkflowMissingToolsError class (extends Error, carries missingToolIds[]); update getRunData() catch to detect missing_tool_ids in the error response and throw the typed error before rethrowSimple() would strip structured data - WorkflowMissingToolsRequest.vue: new component — shows a button with count badge, a GModal confirmation dialog, and uses localStorage keyed on the sorted tool-ID set to disable the button after a successful request (avoids hammering); hidden for anonymous users and when the feature flag enable_tool_request_form is false; falls back gracefully when workflowName is not yet available - WorkflowMissingToolsRequest.test.ts: 25-test suite covering visibility rules (feature flag, anonymous, empty IDs), cancel path, payload shape, singular/plural copy, localStorage deduplication, storage-key sorting, in-flight disabled state, error recovery/retry, and modal re-open - WorkflowRun.vue: import and render WorkflowMissingToolsRequest inside the danger BAlert when a WorkflowMissingToolsError is caught; call getWorkflowInfo() as a best-effort fallback to populate workflowName on both the standard and instance (props.instance) code paths - schema.ts: add tool_ids and workflow_name to ToolRequestFormData type
Remove requester_name and requester_email from ToolRequestFormData and populate them server-side from trans.user to prevent spoofing. Update integration tests and regenerate API schema accordingly.
…s URL - Drop requester name/email inputs (now filled server-side) - Replace bootstrap inputs with Galaxy's FormElement for consistent UI - Validate tool URL: only https:// links accepted - Update tests to use suppressExpectedErrorMessages helper instead of silencing all console errors
tool_request notifications are admin-targeted; exposing the setting to regular users would let them opt out of receiving notifications they cannot receive anyway, and admins opting out would defeat the purpose.
Avoid rendering the URL as a clickable link to prevent admin-targeted URL injection attacks where a malicious user crafts a misleading href.
Add ToolRequestEmailNotificationTemplateBuilder and register it in email_templates_by_category. Add HTML and plain-text email templates for tool installation request notifications.
…nd tests The field was removed from ToolRequestFormData (server now reads it from the authenticated session). Drop the stale payload property and the 'Galaxy user' fallback test that covered it.
…ing false alert class
…ver-side Both fields are now populated from the authenticated user on the backend (trans.user.email / trans.user.username) rather than from the form payload, preventing spoofing. requester_email is always present for registered users; requester_name is optional since usernames are not always set. - ToolRequestNotificationContent: swap required/optional for the two fields - NotificationCard: always render email, conditionally prepend name - Email templates: same conditional rendering logic - schema.ts: regenerated to match
3b5d3da to
21aad9f
Compare
|
I will rename this feature to |
When `force_sync=True` is passed to `send_notification_internal`, the
notification row was created and SSE events fired, but email (and any
other async channel) was never dispatched because the channel plugins are
only registered when celery tasks are enabled. This meant tool-request
notifications — which the service layer explicitly forces to sync so the
client gets a response ID — would not deliver email to admins.
Changes:
- Add `_dispatch_notification_via_channels` to `NotificationManager` that
marks the row dispatched and then calls `_dispatch_notification_to_users`,
mirroring the pattern in `dispatch_pending_notifications_via_channels`.
- Call it inside `send_notification_internal` when `force_sync=True` and
celery is enabled (i.e. channels are registered).
- Deduplicate the sync/async branching that was duplicated between
`NotificationService.send_notification_internal` and the manager: the
service now delegates directly to the manager.
- Make `contact_email` and `notification_settings_url` on
`NotificationContext` `Optional` (the builder already produced `None`
for both when config is absent; templates already guard with `{% if %}`).
- Add unit test asserting email channel fires on `force_sync=True`.
- Add integration test with mock SMTP verifying end-to-end email delivery
for tool-request submissions when celery tasks are enabled.
Closes #21758
Summary
Adds a Tool Request Form that allows logged-in users to request new tools to be installed on a Galaxy instance. Submitted requests are delivered to all admin users via Galaxy's existing notification system.
This PR also extends the feature to handle workflow runs with missing tools: when a workflow cannot be run because required Tool Shed tools are not installed, a one-click "Request Installation" button appears inline on the workflow run page so users can notify admins without filling out the full form.
Feature 1 — Tool Request Form (ToolBox panel)
User Flow
enable_tool_request_form: trueis set in the Galaxy config. The button is only shown when a tool search returns no results.Form Fields
Tool Information (required fields marked *):
Requester Information:
Requester name and email are populated automatically from the authenticated user's account — they are not editable in the form to prevent spoofing.
The Submit Request button is disabled until both required fields (Tool Name, Description) are filled.
Feature 2 — Workflow Missing-Tools Install Request
User Flow
POST /api/tool_request_formendpoint is called with the full list of missing tool IDs and the workflow name. All admins are notified.localStorage(keyed on the sorted tool IDs) so the button stays suppressed across page reloads — preventing duplicate requests.Admin Side — Notification Inbox
When a tool request is submitted, all admin users receive a notification in their Galaxy notification inbox. Each notification shows the tool name, description, URL (if provided), and the requester's account name and email — populated automatically from their authenticated session.
Architecture additions
lib/galaxy/managers/workflows.pyMessageExceptionnow includesmissing_tool_idsinextra_error_infoso the list is serialised into the JSON error responselib/galaxy/schema/notifications.pytool_idsandworkflow_nameadded toToolRequestNotificationContentlib/galaxy/webapps/galaxy/services/tool_request_form.pytool_idsandworkflow_nameadded toToolRequestFormDataand wired through to the notificationclient/src/components/Workflow/Run/services.jsWorkflowMissingToolsError(extendsError, carriesmissingToolIds[]);getRunData()throws it beforerethrowSimple()can strip the structured dataclient/src/components/Workflow/Run/WorkflowMissingToolsRequest.vueclient/src/components/Workflow/Run/WorkflowRun.vueWorkflowMissingToolsError, populatesmissingToolIds, rendersWorkflowMissingToolsRequest; callsgetWorkflowInfo()as a fallback on both standard andinstancecode paths to ensure the workflow name is availableclient/src/api/schema/schema.tstool_idsandworkflow_nameadded toToolRequestFormDatatest/integration/test_tool_request_form.pytest_workflow_install_request_with_tool_ids— submits withtool_ids+workflow_name, verifies both fields appear in the admin notificationclient/src/components/Workflow/Run/WorkflowMissingToolsRequest.test.tsFull Architecture
client/src/components/Panels/ToolBox.vueconfig.enable_tool_request_form && !isAnonymousand search has no resultsclient/src/components/Tool/ToolRequestForm.vueclient/src/api/toolRequestForm.tsPOST /api/tool_request_formcalllib/galaxy/webapps/galaxy/api/tool_request_form.pylib/galaxy/webapps/galaxy/services/tool_request_form.pylib/galaxy/schema/notifications.pyToolRequestNotificationContentin notification unionlib/galaxy/config/schemas/config_schema.ymlenable_tool_request_form(default:false)The feature requires both
enable_tool_request_form: trueandenable_notification_system: trueingalaxy.yml.How to test the changes?
I've included appropriate automated tests.
This is a refactoring of components with existing test coverage.
Instructions for manual testing are as follows:
Tool Request Form (ToolBox):
config/galaxy.yml:Workflow Missing-Tools Button:
License