Release PR - #1495
Merged
Merged
Conversation
…akeholder documents (#1480) * Add init files for agent work * Add new user stories for the account settings * feat(): add Associated Datasets UI prototype Add a pure UI prototype for the 'Connect datasets with research projects' feature (Stories 01-08), demonstrating search, connect, view, sync, and remove interactions for open and restricted datasets on InvenioRDM instances (Zenodo, FDAT). Files added: - datamanager-app/.../demo/AssociatedDatasetsDemo.java: Development-profile-only Vaadin view at /test-view/associated-datasets with a Public/Restricted tab layout, mock data, and inline actions. - docs/feature-vision-associated-datasets.md: Reformatted feature vision document with background, scope, and user stories. - ui-screenshot(-restricted).png: Visual reference of the prototype for PR review. The demo uses existing project UI patterns (Section, ActionBar, InfoBox, Tag, TabSheet) and requires no real backend integration. * docs: add ADR infrastructure, stakeholder documents, and InvenioRDM prototype - ADR infrastructure: introduce docs/adr/ with MADR template (from https://adr.github.io/madr/), README with rules and index, and LICENSE. Provides a lightweight, append-only log for recording significant architectural decisions. - Governance: update AGENTS.md §11.1 (Story Lifecycle) to define the two decoupled surfaces (stakeholder space / developer space) and the PO as handoff actor. Wire ADR references into docs/requirements.md and docs/requirements-guide.md. - Stakeholder documents: add feature brief and resource information spec for the InvenioRDM dataset connection feature. - Prototype: extend AssociatedDatasetsDemoV2 with account settings workflow for InvenioRDM credential management. * docs: note FAIR Signposting mechanism has been replaced with API-based integration The InvenioRDM FAIR Signposting serialization bug is upstream and will not be ready in time for a Data Manager feature delivery. The current integration uses the InvenioRDM OpenAPI directly. Correction notes were added to the markdown renderings of the stakeholder brief to preserve the original wording while clearly flagging the change. * fix: revert accidental removal of production dialog.css styles The file was modified on the prototype branch with ~868 lines removed. Those styles are used by production dialogs across the application and must not be removed. Revert to the origin/development version of the file. Prototype-specific CSS additions, if needed, belong in a prototype-only stylesheet — not in the production dialog.css component stylesheet. * Update bundle * Remove development artifacts * Remove development artifact: ui-screenshot.png --------- Co-authored-by: Sven Fillinger <sven.fillinger@qbic.uni-tuebingen.de>
* feat(FEAT-DATSET-01): connect associated InvenioRDM datasets with projects (draft) Part of #1467 / #1466. Honors ADRs 0001, 0002, 0003, 0004. Note: commit.gpgsign=true is set in ~/.gitconfig, but the signing key ~/.ssh/id_qbic is missing locally. This commit is therefore unsigned. Re-enable signing after the key is restored. What works so far: - New 'DATASETS' link on project navigation (below SUMMARY) - @route 'projects/:projectId?/datasets' under ProjectMainLayout - ConnectedResourcesComponent with expandable detail rows - Empty state when project has no connected datasets - Sliding connect sidebar (per prototype V2) with: - InvenioRDM instance selector (Zenodo, FDAT from config) - Search field + Search + Clear - Multi-select results grid with lazy-loading + loading indicator - Optional experiment association (AC9) - Connect Selected footer button - Domain aggregate: AssociatedDataset + sealed ResourceMetadata hierarchy (source-specific metadata in JSON blob; 4 universal SQL columns) - Application service + DatasetSource port with actingUserId (decryption boundary stays in infrastructure per ADR-0002 D1) - InvenioRDM client calling REST API v12 (bounded retry, 5xx/429 only; 4xx not retried) - JPA repository + DB DDL (sql/migrations/create-associated-dataset.sql) - 3-tier migration docs (NEXT.md + README index) * fix(datasets): gate sidebar grid fetch on explicit user action The sidebar was still blocking on open because: - loadInstances() auto-selects the first repository on open - that auto-selection fires the value-change listener - which calls refreshSearchResults() → refreshAll() - which makes Vaadin's lazy grid ask for the first page - fetchPage then makes a synchronous HTTP call to InvenioRDM - the whole chain runs on the server thread, freezing the UI Introduce a `searchInitiated` flag set ONLY by explicit user actions (Search button click, Enter in the search field, Clear button click). - fetchPage() short-circuits to `Stream.empty()` when the flag is false — no HTTP call is made for the grid's auto first-page fetch - The instance-selector value-change listener also gates on the flag so the auto-select during open() does not refresh the grid - `close()` resets the flag, so each new open is instant again - Scrolling still works lazily once a search has been initiated AC3 (paginated results when no query) stays satisfied: the very first search can be with an empty search term. * Fix grid data provider query contract violation Add query.getOffset() and query.getLimit() calls before early returns in ConnectDatasetSidebar.fetchPage() to satisfy Vaadin's contract requirements. This resolves the 'getLimit() or getPageSize() method has not been called' error that occurred when the data provider returned early before accessing query parameters. * feat(datasets): Add welcome message overlay for empty grid states Empty grid states are user-unfriendly and don't guide users on what to do next. Added a welcome message overlay that displays before the user initiates their first search: - Search icon with large font size - Title: 'Search for datasets' - Subtitle explaining what the search does - Centered, with proper spacing and secondary text colors The overlay: - Uses absolute positioning within the results container - Covers the empty grid area without affecting layout - Disappears when user performs any search action: - Clicks Search button - Presses Enter in search field - Clicks Clear button - Has higher z-index to appear above the grid This improves UX by: - Providing clear next steps to users - Reducing confusion about the empty state - Maintaining smooth interactions (no layout shifts) - Keeping the sidebar opening instant (no premature HTTP calls) Honors FEAT-DATSET-01 UX requirements. * refactor(datasets): implement async search with progressive UI feedback Refactored ConnectDatasetSidebar to move HTTP fetch out of the lazy data provider and into an async background thread. This allows the loading indicator to be shown immediately via @Push, providing better user feedback during search operations. Key changes: - Removed HTTP call from fetchPage() - now only slices cachedResults - Made refreshSearchResults() fully async with background Thread - Added cachedResults List<SearchHit> to store search results - Added searchInProgress flag to prevent duplicate concurrent searches - Search controls (button, field, instanceSelector) disabled during search - Loading indicator shown immediately, hidden when results are pushed back - Welcome message hidden when search is initiated This resolves the UX issue where the loading indicator was not visible because everything happened in a single Vaadin response. Now the loading state is pushed immediately, then the results are pushed when the HTTP call completes. Implements Option A: load all results in one background fetch with page=0, pageSize=100. Virtual scrolling still works via cachedResults. * feat(datasets): improve search loading indicator UX Replaced the basic ProgressBar with a more user-friendly loading overlay: - Animated hourglass spinner (⌛) with CSS rotation - Clear 'Searching for datasets...' message - Helpful hint 'This may take a few seconds' - Full-width overlay with centered content - Higher z-index (2) to ensure visibility This addresses the UX issue where the loading indicator was not visible due to being rendered in the same response as the data. The async search implementation from the previous commit ensures the loading state is pushed immediately via @Push, making this indicator now effective. The visual feedback now clearly communicates to users that: 1. A search is in progress 2. The operation may take some time 3. The system is actively working on their request * fix(datasets): capture user identity before async thread to fix security context issue The async search implementation was failing because SecurityContextHolder uses ThreadLocal storage, which is not propagated to background threads. When we moved the HTTP call to a background thread, the security context became unavailable. Solution: Capture the current user ID in the main thread BEFORE launching the background thread, then pass the captured value to the background thread. This ensures the security context is accessible when we need to identify the user for the dataset search operation. * fix(datasets): use security-context-aware task executor for async search Replace manual Thread creation with CompletableFuture.supplyAsync using the DelegatingSecurityContextAsyncTaskExecutor from Spring's AsyncConfig. This ensures the security context is properly propagated to the async thread, fixing the 'Search failed: Cannot invoke Authentication.getPrincipal()' error that occurred when searching for datasets. The taskExecutor bean is already configured in AsyncConfig to wrap the ThreadPoolTaskExecutor with DelegatingSecurityContextAsyncTaskExecutor, which automatically propagates the SecurityContext to async threads. Changes: - Inject Executor (taskExecutor) into ConnectDatasetSidebar constructor - Use CompletableFuture.supplyAsync() with taskExecutor instead of new Thread() - Add proper error handling with exceptionally() callback - Update AssociatedDatasetsMain to inject and pass the taskExecutor This follows the Vaadin idiomatic approach for async operations with security context propagation. * fix: Use relevance-based sorting for search queries in InvenioRDM client When searching for datasets (e.g., 'Data Manager QBiC'), the results were returned sorted by newest first instead of by relevance. This caused inconsistency with Zenodo's UI where the most relevant result appeared first. The issue was that buildSearchUrl() always appended '&sort=newest', which forced the API to sort by creation date even when a search query was provided. Fix: Only use '&sort=newest' when there's no search query (browsing mode). When searching, rely on the API's default relevance-based sorting to match Zenodo's behavior. This ensures users get the same result ordering as in Zenodo's web UI. * fix: Ensure welcome message shows on sidebar open, not loading indicator Reset UI state in open() method to ensure the sidebar always starts with the welcome message visible, not the loading indicator. Changes: - Explicitly reset searchInitiated = false - Hide loading indicator - Show welcome message - Refresh grid data provider - Enable all controls This ensures users see the 'Search for Datasets' prompt when opening the sidebar, with the 'Searching...' overlay only appearing when they actually initiate a search. * fix(datasets): Apply overlay class name to experiment selector dropdown The experiment selector dropdown was being clipped by the sidebar's z-index, making it impossible to see the full experiment list. This issue was already fixed for the dataset repository dropdown, so we apply the same solution: setOverlayClassName("connect-dataset-sidebar-overlay") The CSS class is defined in the theme and ensures the dropdown overlay renders above the sidebar panel. * feat(FEAT-DATSET-01): resolve UUIDs to human-readable display names Introduce an API-contract DTO to replace the raw domain entity leaking into the UI layer and fix several related UX issues. UI domain-entity layer violation fix: - New ConnectedDatasetView DTO in the associated_dataset application package, carrying resolved display names and flat source-specific fields so the view never touches domain model classes. - AssociatedDatasetService.listConnectedDatasetViews() replaces the old listConnectedDatasets(). It collects distinct user IDs and experiment IDs from the batch, resolves each exactly once via UserInformationService and ExperimentInformationService, then maps aggregate to DTO. O(U + E) lookups regardless of row count. - ConnectedResourcesComponent now consumes ConnectedDatasetView and has no imports from the domain model package (AccessLevel remains via enum). UX improvements: - Connected By and Linked Experiment detail rows now show the users full name and the experiments display name instead of raw UUIDs. - Linked Experiment name is a clickable anchor (target=blank) that opens the experiment view in a new browser tab via AppRoutes.ProjectRoutes.EXPERIMENT. - Sidebar panel width is now responsive: width: min(55 percent, 720px); min-width: 460px; max-width: 100vw Title text is clamped to 2 lines with a CSS ellipsis and full title exposed as a native HTML title attribute for hover tooltip. - Connected Resources view now occupies the full available content width (new .main.project.datasets grid override collapses the two-column project grid into a single 1fr column). InvenioRDM version resolution fix (single-version records): - Zenodo does not populate metadata.version for records with only one version. The authoritative source is metadata.relations.version[] which is always present but 0-based. version() and recordVersion() now fall back to relations.version[0].index + 1, producing "v1" for the first (and possibly only) version. - Removed the bogus Versions DTO and its JsonProperty("versions") mappings on Hit/RecordResponse: links.versions is a URL string in the InvenioRDM response and never useful to map. Co-authored-by: sven1103-agent <sven.broja@qbic.uni-tuebingen.de> * feat(FEAT-DATSET-01): wire toast notifications and add collaborator email directive Toast notification factory wiring - Inject MessageSourceNotificationFactory into ConnectDatasetSidebar via AssociatedDatasetsMain (passed through constructor). - Replace raw "new Notification(...)" calls with factory-produced Toasts (BOTTOM_START positioning, i18n, correct theme alignment). - Add four toast keys to toast-notifications.properties: dataset.connected.success (success, count parameter) dataset.connected.failure (error, count parameter) dataset.experiments.failed (error, static) dataset.search.failed (error, static) - Show the sidebar is now fully integrated with the apps notification system instead of rendering ad-hoc BOTTOM_END Vaadin notifications. Email notification for collaborators (Task 6) - New directive InformProjectCollaboratorsAboutDatasetConnection (package: life.qbic.projectmanagement.application.policy.directive) that subscribes to AssociatedDatasetConnectedEvent. - On connect, every collaborator on the project is looked up via ProjectAccessService.listCollaborators() and emailed, with the actor excluded from the recipient list (no self-notification). - Emails are dispatched via JobRunr background jobs so the domain handler does not block on SMTP. - New Messages.datasetConnectedToProject(...) template. - New AssociatedDatasetConnectedPolicy wires the directive via DomainEventDispatcher, registered as a @bean in AppConfig.java. AC8 is now fully satisfied. * feat(FEAT-DATSET-01): make dataset connect non-blocking via reactor + Option B UX Async service (reactor) - Introduce AsyncAssociatedDatasetService API and AsyncAssociatedDatasetServiceImpl implementing non-blocking single and batch connect endpoints on top of the blocking AssociatedDatasetService via Mono.fromCallable + Schedulers.boundedElastic(). - connectDatasets(...) fans out with a parallelism cap of 3, matching the anonymous per-IP rate limit of public InvenioRDM instances (e.g. Zenodo). - Per-request 30s timeout; on timeout or network failure the Mono emits a ConnectDatasetResponse carrying ConnectDatasetError so the reactive stream never terminates in error and the UI can tally successes vs failures cleanly. - Wire the async service as a Spring @bean in AppConfig.java. Option B UX for dataset connect - The ConnectDatasetSidebar now stays open during a batch connect and shows a spinner overlay on the results grid ("Connecting datasets..."), leaving the close button reachable. - Per-row state: row card opacity drops to 0.5 while connecting; a green check-mark appears on success and a red cross on failure — rendered immediately as each Mono emits onNext. - Atomic success/failure counters are updated off the UI thread and flushed back via UiHandle.onUiAndPush for safe Vaadin access. - After all responses have arrived (onComplete), a 600ms settling delay runs before the overlay hides, the grid refreshes (status map cleared), the sidebar closes, the success/failure toasts fire, and ConnectedResourcesComponent is refreshed via the DatasetsConnectedEvent — guaranteeing the list reflects the new state before the user sees it. * refactor(FEAT-DATSET-01): move async connect into AssociatedDatasetService + fix SecurityContext propagation - Delete redundant AsyncAssociatedDatasetService and its implementation. They duplicated the API contract for no abstraction benefit. - Expose connectDatasetAsync(ConnectDatasetRequest): Mono<...> and connectDatasets(List<ConnectDatasetRequest>): Flux<...> directly on AssociatedDatasetService, keeping the sync connectDataset method intact (matches the precedent where AsyncProjectService owns its interface but AssociatedDatasetService just needs one extra transport method). - Fix SecurityContext propagation bug that caused per-record AccessDenied on the worker thread. The new methods use .contextWrite(ReactiveSecurityContextUtils.reactiveSecurity(securityContext)) - the exact pattern used by AsyncProjectServiceImpl - so Spring Security reactive AOP correctly sets the ThreadLocal before @PreAuthorize evaluates on connectDataset. - Update ConnectDatasetSidebar, AssociatedDatasetsMain, and AppConfig to consume AssociatedDatasetService directly. - Inner records ConnectDatasetRequest and ConnectDatasetResponse live on AssociatedDatasetService now. * feat(FEAT-DATSET-01): persist SUCCESS indicators until user resets sidebar Strategy B: connected rows now stay visually marked until the user either closes the sidebar or starts a new search. Per-row state changes: - SUCCESS rows get a success-coloured background tint (var(--lumo-success-color-10pct)) and a leading "Connected ✓" Tag badge (SUCCESS colour) so they are distinguishable at a glance even before reading the toast banner. - ERROR rows keep the red cross icon in the top row. - PENDING rows remain at 0.5 opacity during connection. Auto-deselect on success: - When a connect succeeds the corresponding search result is auto-deselected from the multi-select grid so it cannot be re- connected by a subsequent "Connect Selected" click. - The footer selection counter ticks down live, giving the user concrete progress feedback (5 → 4 → 3 → 2 left). Lifecycle of the status map: - REMOVED `rowConnectionStatuses.clear()` from the Flux `onComplete` (it was the root cause of the brief flash — the clear ran before the grid refresh, so icons vanished ~600ms before the toast fired). - ADDED the clear to `close()` alongside `resultsGrid.deselectAll()` so state is properly discarded when the sidebar is dismissed. - ADDED the clear to `refreshSearchResults()` (new search) so stale connection state from a previous connect does not bleed into a fresh InvenioRDM result set. Removed the explicit `close()` call from `onComplete` — the sidebar now stays open long enough for the user to confirm the connected rows, and closes naturally when the toast auto-dismisses (~5s) or on user click. * fix(FEAT-DATSET-01): replace CompletableFuture.delayedExecutor with Mono.delay in onComplete CompletableFuture.delayedExecutor(...) uses the common fork-join pool, which can saturate under concurrent Vaadin push load and silently drop runnables. Symptoms in production: spinner overlay stayed forever, no toast fired, sidebar never closed. Replace with Mono.delay(...), which uses Reactors boundedElastic scheduler already configured and exercised by the rest of the connectDatasets pipeline. This is the same mechanism the codebase already uses in AsyncProjectServiceImpl. * fix(FEAT-DATSET-01): stop relying on Flux.onComplete for post-connection UI work The deprecated 3-arg subscribe(onNext, onError, onComplete) has a known reactor-core 3.x defect: onComplete is silently skipped when subscribeOn(boundedElastic) is combined with onErrorResume, which is exactly our pipeline. Symptoms: spinner overlay "Connecting datasets..." stays forever, no toast, sidebar never closes. Also stop relying on Mono.delay-scheduler close. Under concurrent push load that scheduled task was dropped silently too. New approach: count completed responses inside onNext via a shared AtomicInteger; when the counter reaches total, trigger onBatchFinished inline on the same worker thread. onBatchFinished posts UI reset + toast via uiHandle.onUiAndPush and schedules close via Mono.delay with explicit error handler. If any individual onNext throws, the catch increments the counter too (so a partial pipeline stall still reaches the finish line) and the last one calls onBatchFinished with all-failure semantics rather than leaving the UI permanently frozen. * fix(FEAT-DATSET-01): simplify connect flow - remove per-row tracking and timer-based close User feedback: "sidebar remains open and then suddenly closes magically; reports failures although the record is connected successfully". Root cause analysis: - Per-row SUCCESS badges and status map added complexity without value. - Mono.delay(5s) for auto-close felt unpredictable and could silently drop under push load. - The "reports failures" issue may be a side-effect of the previous buggy onComplete-path where the timer and completion callback could fire out-of-order. Changes: - Removed `rowConnectionStatuses` map and `ConnectionStatus` enum. - Removed per-row SUCCESS badge and tinting from `buildSearchResultCard`. - Removed automatic selection change per response. - Replaced timer-based close with immediate `close()` call in `onBatchFinished`. - Simplified counter logic: track successes and failures inline in `subscribe onNext`; when `completedCount == total`, close and toast. - Kept the spinner overlay while connecting (grid hidden), so user sees continuous "Connecting datasets..." feedback. - Added explicit try/catch around individual response handlers so the batch always terminates even if a handler throws. * fix(FEAT-DATSET-01): fix false-failure toast and simplify connect UX Two bugs fixed: 1. Wrong failure count in toast: `connectDataset` step 5 (forward cached domain events to the global dispatcher) was not protected by a try/ catch. If event dispatch threw (e.g., subscriber issue), the exception propagated up through Mono.fromCallable, was caught by the onErrorResume, and mapped to CONNECT_FAILED — even though the dataset had already been persisted in step 4. The user saw their record in the Connected Resources list but also got a "could not be connected" toast. Fix: wrap the event dispatch in try/catch so a dispatch failure is logged but does not fail the connect itself. 2. Sidebar closed unpredictably: previous code used `CompletableFuture.delayedExecutor` / `Mono.delay` to schedule the close. Under concurrent Vaadin pushes these tasks could be silently dropped, leaving the sidebar permanently open, OR fire immediately after a busy push burst, closing the sidebar in a way that felt "magical" to the user. Fix: remove the timer; close the sidebar synchronously inside onBatchFinished immediately after the batch finishes. User always has the explicit × close affordance as fallback. Along the way removed the per-row SUCCESS badge logic and the rowConnectionStatuses tracking map — these added complexity without observable UX value (the sidebar now closes immediately, so per-row state never gets visible for more than a frame). * feat(FEAT-DATSET-01): improve connected resource card scannability - Promote resource type from gray detail row to a CONTRAST badge in the card header row so users can scan resource types at a glance. - Remove meaningless dash placeholders for missing remote properties (version, creator, community, linked experiment, connected by/on); fields are now rendered only when the provider supplies data. - Fuse 'Connected by' and 'Connected on' into a single italic attribution line ('connected on <date> by <name>') to reflect that they are semantically the same provenance event. - Refactor addDetailCell() to be a no-op on null values so future callers cannot accidentally reintroduce dash placeholders. * feat(FEAT-DATSET-01): order connected datasets by connected-on descending Connected resources were previously returned in undefined database order (no ORDER BY in the JPQL query). This was non-deterministic and could shift after index rebuilds or vacuuming. Default sort is now connected_on DESC — newest connections first — which matches the mental model of 'what was added to my project most recently'. The Sort is applied at the JPA infrastructure layer so the domain repository interface stays Spring-independent; switching the sort key later is a one-line change in the impl. * feat(FEAT-DATSET-01): improve search result card interaction - Make row-click toggle selection: clicking anywhere on a card row (not just the checkbox) now selects/deselects the hit. The checkbox continues to work independently. Cursor pointer on cards gives explicit affordance that the row is interactive. - Replace plain-text "PID: x.yz/..." with a real clickable Anchor pointing at the DOI resolver URL. The record opens in a new tab (target=_blank) so users can verify the dataset before connecting without losing their current search context. - Drop the redundant "PID:" label since the URL itself is unambiguous. * fix(FEAT-DATSET-01): align provider-tag colors between sidebar and connected list Search result cards in the sidebar rendered every provider with PRIMARY (blue), but the connected-resources list differentiates Zenodo (PRIMARY) from other providers (TEAL) so the two views now share the same visual language. Switching a card from search to connected-list no longer changes its perceived visual weight. * refactor: extract ResourceProviderTag factory for centralized provider styling - Replace inline provider tag construction (with Zenodo=PRIMARY / other=TEAL logic) in ConnectedResourcesComponent and ConnectDatasetSidebar with a single ResourceProviderTag factory. - Provider tags now use TagColor.NEUTRAL instead of color-coding, reducing visual noise and eliminating the implicit hierarchy between providers. - Add Javadoc explaining the design rationale. * fix(FEAT-DATSET-01): align badge order between search results and connected list Both views now render badges in the same order: provider first, then access status. Previously the sidebar showed access then provider, while the connected-resources list showed provider then access — a minor but unnecessary inconsistency when moving between the two. * Finalise user story * refactor(datasets): harden AssociatedDatasetService — typed exceptions, YAGNI cleanup, Instant timestamps, pure-CSS four-dots spinner The service layer now throws typed, user-friendly exceptions instead of re-exposing infrastructure-level ApplicationExceptions with URLs and status codes leaking to the UI. The reactive pipeline catches and logs every escaped error, so connect failures are always traceable. ## Exception contract (new) * AssociatedDatasetServiceException (sealed base) - DatasetSourceNotFoundException – unknown repository ID - DatasetSourceUnavailableException – network / HTTP / parse failures * Each carries a user-friendly message; no infrastructure details leak. * connectDatasetAsync: onErrorResume now logs the cause instead of silently swallowing — fixes the 'toast shown, nothing in the log' bug. * Duplicate-check (isActiveConnectionPresent) wrapped in try/catch so persistence errors surface through the typed error, not through the reactive catch-all. * Stale 'experimentId must not be null (use Optional.empty())' requireNonNull from the Optional→@nullable refactor removed. * UiExceptionHandler surfaces AssociatedDatasetServiceException messages directly as the dialog body (title + translated content). * toast-notifications.properties: dataset.search.failed.message.text now uses {0} so the user sees the service-specific message. ## YAGNI: embargoUntil removed from InvenioRdmResourceMetadata FEAT-DATSET-01 covers public datasets only; embargo tracking is scoped to FEAT-DATSET-14. The record field + the matching 'embargoUntil == null' check in deriveAccessLevel() are redundant — EMBARGOED status already fails the PUBLIC-only check. * Record field removed; deriveAccessLevel() simplified + documented. * InvenioRdmDatasetSource.embargoUntil() helper removed. * InvenioRdmClient.Embargo + its JSON binding removed. * @JsonIgnoreProperties(ignoreUnknown = true) on the record so that rows persisted by older app versions (with 'embargoUntil' in the resource_metadata JSON) still deserialize correctly — fixes the 'Error applying AttributeConverter' bug on reopening the page. * ResourceMetadataConverter now prints a payload preview on failure. * Regression spec InvenioRdmResourceMetadataJsonSpec covers the legacy-payload deserialization path + unknown future fields. ## Instant instead of LocalDateTime for connection timestamps connectedOn and lastSyncedAt were LocalDateTime — a timezone-naive type that produces incomparable records in multi-JVM deployments (Berlin JVM vs Kolkata JVM write different wall-clock values for the same instant), drifts silently on redeploy to a different host, and is ambiguous at DST fall-back. Replaced with Instant; DB columns changed from DATETIME(3) → TIMESTAMP(3); hibernate.jdbc.time_zone=UTC added to the data-management EM config so the full stack is UTC (Java → JDBC → MariaDB, all consistent). The ConnectedResourcesComponent view extracts LocalDate via LocalDate.ofInstant(offset=UTC) for display. ## Application DTOs no longer leak the domain AccessLevel enum ConnectedDatasetView and SearchHit exposed AccessLevel to the UI layer, forcing the view to import a domain type to read one field. Changed to 'boolean isPublic'; translation from AccessLevel happens at the service boundary (AssociatedDatasetService + InvenioRdmDatasetSource). DataSetTagFactory now accepts boolean and has zero domain imports, so it is a pure UI-layer utility. ## DataSetTagFactory — centralize all dataset tag styling Inline tag creation for provider / dataset type / access badges in ConnectedResourcesComponent and ConnectDatasetSidebar now routes through DataSetTagFactory. Access type badge (Public/Restricted) is a new TagType with semantic SUCCESS/WARNING color. ## CSS: inline styles → reusable classes in ConnectDatasetSidebar ~60 inline getStyle().set(...) calls extracted to: * New connect-dataset-sidebar.css with scoped .cds-* classes * Reusable utilities added to all.css: .clamp-1/2/3-line (multi-line -webkit-line-clamp), .overlay-center-fill (position:absolute + inset:0 + centered flex layout for loading/welcome overlays). * Only runtime display:none/flex/block toggles stay inline. ## CSS spinner — Temani Afif Spinner II (t_afif/pen/yLMXBRL) Replaces the clock-emoji innerHTML spinner with a pure-CSS four-dots orbit spinner. Both rings have explicit, named color tokens (--cds-spinner-outer, --cds-spinner-inner defaulting to --lumo-primary-color) so the two visible dot colors are discoverable in the source; inner dots derive an accent via hue-rotate(45deg) to stay harmonious with any Lumo primary choice. The outer/inner rings rotate at different rates for the characteristic orbit effect. ## Test updates * InvenioRdmClientParsingSpec: removed embargo-parsing tests (embargo infrastructure is gone) and the rec.access.embargo.active assertion (Embargo class removed from the client). JSON fixtures kept as-is — Jackson's @JsonIgnoreProperties ignores the key. * New InvenioRdmResourceMetadataJsonSpec — covers legacy-payload deserialization (embargoUntil still in JSON) and unknown future fields are silently ignored. * feat(invenio): replace ApplicationException with typed InvenioRdmException hierarchy Add a structured exception hierarchy to the InvenioRdmClient API: - InvenioRdmException (abstract base) — shared URL accessor for all client failures - InvenioRdmPermanentException — 4xx errors not retried per ADR-0002 §9 (401, 403, 404, etc.), carries statusCode - InvenioRdmTransientException — retry exhaustion for 5xx/429/network, carries statusCode, attempts, and lastError - InvenioRdmResponseParsingException — JSON deserialization failure, carries targetType - InvenioRdmInterruptedException — thread interrupted during retry sleep Update method signatures with explicit @throws declarations and Javadoc. Update InvenioRdmDatasetSource callers to catch base exception type and check for 404 via instanceof pattern matching instead of string-based status code detection. * Latest refactor changes from review * Fix OIDC issue * Address change requests * Add change requests * Remove unused imports * Remove unused imports --------- Co-authored-by: Sven Fillinger <sven.fillinger@qbic.uni-tuebingen.de> Co-authored-by: sven1103-agent <sven.broja@qbic.uni-tuebingen.de>
…1486) * feat: FEAT-DATSET-03 — remove connected dataset Implement the 'Remove a connected open, published dataset' story (#1469) of the FEAT-DATASET-CONNECTION feature (#1466). Changes by task: Task 0 — UI foundation: AlertDialog factory * New centralized, intent-driven dialog factory (DANGER/WARNING/ERROR/INFO) built on AppDialog.small(), IconFactory, and DialogFooter helpers * AlertDialog.danger() shortcut for destructive-action pattern * Fluent builder API for full control * Unit tests (12 cases) Task 1 — Domain: emit AssociatedDatasetRemovedEvent on remove() * AssociatedDataset.remove(removedByUserId) transitions state to REMOVED and dispatches an AssociatedDatasetRemovedEvent * Actor is the user who performed the removal (may differ from original connector) * New domain-level spec: AssociatedDatasetRemoveSpec (4 tests) Task 2 — Application: AssociatedDatasetService.removeDataset() * Find → remove → save → forward-events pipeline * Returns Result<AssociatedDatasetId, RemoveDatasetError> * Pre-existing AssociatedDatasetServiceRemoveSpec (6 tests) now green Task 3 — Policy & notification directive * AssociatedDatasetRemovedPolicy subscribes to removal events * InformProjectCollaboratorsAboutDatasetRemoval directive emails every project collaborator except the actor (JobRunr background job) Task 4 — Email template * Messages.datasetRemovedFromProject() template added Task 5 — Toast notifications * dataset.removed.success / .failure / .already / .notfound * dataset.removing.in-progress (pending-task toast) Task 6 — UI: remove button + async flow * Per-card Remove button (VaadinIcon.TRASH, WRITE-only, right-aligned) * setWriteAllowed(boolean) propagates project ACL from the view * AlertDialog.danger() confirmation with plain-text message * Blocking removeDataset() wrapped in CompletableFuture on ForkJoinPool.commonPool; UI thread returns to idle immediately * Pending-task toast shown during removal; replaced by success/error toast on completion via UiHandle.onUiAndPush() * Non-critical errors (NOT_FOUND, ALREADY_REMOVED) refresh silently Task 7 — Wiring * AppConfig registers the AssociatedDatasetRemovedPolicy bean Total: 308+ tests pass (0 failures, 0 errors). Closes #1469 * refactor: use reactive removeDatasetAsync instead of raw CompletableFuture Mirror the connectDatasetAsync pattern for removal: the new AssociatedDatasetService.removeDatasetAsync(String, String) returns Mono<Result<...>> and wraps the blocking call with boundedElastic, contextWrite, timeout, and onErrorResume — exactly like connectDatasetAsync. ConnectedDatasetsMain no longer rolls its own CompletableFuture + ForkJoinPool. It now subscribes to the Mono and reacts via onNext / onError callbacks, hopping back to the UI thread through the existing UiHandle. No functional change — only the concurrency mechanism changes: * service API: add removeDatasetAsync() * view: delete CompletableFuture/ForkJoinPool plumbing, use Mono pattern * Address change requests * Remove time-out param
- NGS tab now produces <date>_<project>_ngs_measurement_dataset_locations.txt - PxP tab now produces <date>_<project>_proteomics_measurement_dataset_locations.txt Fixes #1444 Co-authored-by: Tobias Koch <tobias.koch@uni-tuebingen.de>
…ctories (#1489) * refactor: migrate all NotificationDialog to AlertDialog and remove factories Replace all NotificationDialog subclasses with AlertDialog patterns: - Deleted 10 NotificationDialog subclasses (dead-simple factories with zero reusable logic): AccessTokenDeletionConfirmationNotification, BatchDeletionConfirmationNotification, MeasurementDeletionConfirmationNotification, QCItemDeletionConfirmationNotification, PurchaseItemDeletionConfirmationNotification, ProjectUserRemovalConfirmationNotification, ExistingGroupsPreventVariableEdit, ExistingSamplesPreventVariableEdit, ExistingSamplesPreventSampleOriginEdit, ExistingSamplesPreventGroupEdit - Deleted CancelConfirmationDialogFactory — callers now use AlertDialog.alert().warning() directly - Deprecated MessageSourceNotificationFactory.dialog() — single call-site inlined as AlertDialog - Converted NotificationDialog.java to deprecated stub throwing UnsupportedOperationException - Migrated UiExceptionHandler to AlertDialog - Updated front-end-components.md with AlertDialog pattern documentation GitHub Issue: #1487 * chore: completely remove NotificationDialog class and deprecated dialog() method - Delete NotificationDialog.java stub entirely - Delete deprecated MessageSourceNotificationFactory.dialog() method and its helper methods (parseTitle, parseConfirmText, DEFAULT_CONFIRM_TEXT) - Remove unused AlertDialog import from MessageSourceNotificationFactory * style: make AlertDialog warning buttons red and shorten button labels - WARNING intent now gets red danger button (same as DANGER) - Shortened button labels: 'Discard' instead of 'Discard changes', 'Cancel' instead of 'Continue editing' - Updated AppDialog.createConfirmDialog() for consistency * Apply changes to AppDialog confirmation * feat(alert-dialog): replace single-word button labels with action-oriented descriptions UX research indicates that AlertDialog confirm/cancel buttons with single-word labels (Confirm, Discard, Cancel, Okay) create unnecessary cognitive load because users must infer the actual outcome from context. This commit improves transparency by making buttons explicitly state their effect: Destructive actions (was: 'Confirm' / 'Cancel'): - 'Remove' / 'Remove user' → 'Keep user' - 'Disconnect' / 'Disconnect dataset' → 'Keep connection' - 'Delete' / 'Delete offer' → 'Keep offer' - 'Delete file' → 'Keep file' - 'Delete batch' → 'Keep batch' - 'Delete measurements' → 'Keep measurements' - 'Delete token' → 'Keep token' Discard-changes confirmation (was: 'Discard' / 'Cancel'): - 'Discard changes' → 'Keep editing' → applied to 6 call sites including internal AppDialog unsaved-changes alert Error acknowledgment (was: 'Okay'): - 'Got it' → applied to 4 error/informational dialogs Every button pair now follows the pattern: both buttons state their outcome. Files changed: - AlertDialog: danger() factory gains cancelLabel parameter - AppDialog: internal createConfirmDialog() uses new labels - 11 call sites updated to use new API - AlertDialogTest: updated for new signature (14 tests, all passing) * Address change requests
…ported and shown entities based on their persistent identifier (#1482) * Introduce Sorting of Samples, Measurements and Raw Data via an Ascending sort of the SampleId and MeasurementIds within the UI grids and the downloaded files (Excel Templates and URL Link List) * Fix URL Filtering not using the sorted list * Outsource Naturalorder comparator into seperate class * Move NaturalOrder Comparison in Helper class and add tests * Make sorting specific for measurement and samples to avoid unnecessary complicated general method. Adjust testing accordingly * Remove unused imports and set static constant as suggested by sonarcode * Address Sonarcloud, Adjust naming schema and make constant static as well * Remove unnecessary code comparator method * Add documentation for the worksheet entity ordering by id
…ng (#1490) * feat(haproxy): add custom 503 maintenance landing page (#1464) * feat(haproxy): add custom 503 maintenance landing page Add a branded HAProxy error page for 503 responses with: - Inline SVG illustration (server rack scene with floating papers, smoke, and animated sparks/LEDs) - Contact information and provider details - Self-contained HTML+CSS (no external dependencies) Fix HAProxy errorfile format requirements: - Use HTTP/1.1 status line (required by HAProxy 2.7+) - Include Content-Length header matching exact body size - Use CRLF line endings throughout (RFC 7230) - Keep body under HAProxy's 16KB error file limit * Add file size constraint to docs --------- Co-authored-by: Sven Fillinger <sven.fillinger@qbic.uni-tuebingen.de> * feat(FEAT-DATSET-09): show connected-dataset summary in project listing Story #1475 (FEAT-DATSET-09) — researchers can now see from their project collection view whether each project has connected datasets, how many, their access status (Open / Restricted), and when the most recent dataset was connected. Clicking the footer navigates directly to the project's ConnectedDatasetsMain view. Changes: - sql: extend project_overview view with 4 aggregate columns sourced from associated_dataset (COUNT, SUM PUBLIC, SUM RESTRICTED, MAX connected_on). Migration script is idempotent and self-contained — no cross-view dependency on project_measurements. - project-management: +4 fields on ProjectOverview entity - UI: card-body and footer rendered as sibling RouterLinks inside a project-card-wrapper Div so each click target drives exactly one navigation. Footer uses theme classes only (no inline styles). - theme: .project-card-wrapper owns shadow/border-radius/margin; .project-overview-item provides card-body padding; .project-dataset- footer provides footer padding + hover affordance. Chevron turns primary-text-colour on hover. - requirements: DATA-R-04/05/06 allocated (new IDs; existing 01-03 for immunopeptidomics preserved). - demo ProjectListingDatasetsDemo.java kept as development-profile prototype sandbox, javadoc marked superseded. Refs: #1475, #1466 (FEAT-DATASET-CONNECTION) * Address change requests * Fix formatter usage --------- Co-authored-by: sven1103-agent <261423644+sven1103-agent@users.noreply.github.com>
#1484) * feat(toast): redesign toast notifications with dark mode design system Redesign the toast notification UI to align with the data-manager style guide and improve visual design, accessibility, and consistency. Functional changes: - Extract levelIcon as a separate field so subsequent withContent calls cannot accidentally destroy the status icon (fix for missing icons). - Use official Vaadin icons (vaadin:check, vaadin:close, vaadin:info) rendered at 14×14px in white inside 28×28 colored circles, instead of the smaller Lumo iconset (no Lumo info icon exists). - Rewrite toast CSS to use design tokens from the style guide instead of hardcoded hex colors; add 14 new CSS variables (--color-primary, --color-error, --color-success, --color-shade-*, --toast-background-color, --toast-text-color, etc.) in variables.css. - Increase link color from #1676f3 to #66A8FF to meet WCAG AA contrast requirements against the dark navy background. - Fix routing/info toast link visibility by adding CSS overrides for RouterLink/Button elements rendered inside the toast. - Use flexbox-based routing container instead of CSS Grid, simplifying the DOM while preserving ellipsis truncation for long messages via child combinator selectors. - Remove max-width for the toast overlay to let content determine width. - Fix pending task titles/subtexts rendering literal HTML markup by using Html wrappers instead of Div for title/subtext in withProgressBar(). - Wrap HTML content in <span> instead of <div style='display:contents'> to eliminate trailing block-level whitespace in toast messages. - Reimplement pendingTaskToast to build progress layout directly instead of delegating to the text-toast path. - Add actionToast factory method for error toasts with a 'Try Again' button. * Address change requests * Address change requests * Exclude local .ssh agent folder from tracking --------- Co-authored-by: sven1103-agent <261423644+sven1103-agent@users.noreply.github.com>
- Create released/v1.14.0.md with 2 pinned migrations: 1. associated_dataset table (FEAT-DATSET-01) 2. project_overview view extension (FEAT-DATSET-09) - Reset NEXT.md to fresh template for subsequent release - Update released/README.md index
…0 migration (#1494) The released migration doc for v1.14.0 only covered the two SQL schema migrations but omitted the new configuration entries in application.properties. Without these, operators upgrading will find the application fails to start. This adds: - A new 'Application properties changes' section before the schema summary - A reference table of all four instance properties (id, display-name, base-url, api-version) - The default config block with the two pre-configured instances (Zenodo, FDAT) - Operator action items (review, add, restart) - Updated post-migration checklist step 1
sven1103
approved these changes
Jul 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Prepare release