Skip to content

Bugfix 13548 dashboard samples error#13577

Merged
KarnaiahPesula merged 9 commits intodevelopmentfrom
bugfix-13548-dashboard-samples-error
Aug 13, 2025
Merged

Bugfix 13548 dashboard samples error#13577
KarnaiahPesula merged 9 commits intodevelopmentfrom
bugfix-13548-dashboard-samples-error

Conversation

@KarnaiahPesula
Copy link
Copy Markdown
Contributor

@KarnaiahPesula KarnaiahPesula commented Aug 13, 2025

Fixes #

Summary by CodeRabbit

  • New Features

    • Dashboard adds environment-sample support: filter by environment material and view counts by material, specimen condition, shipment status, and test result.
    • Totals and breakdowns merge human and environment data where applicable; sample material filter now lists both human and environment materials.
    • Epi curve restricted to human samples; “Humans” info tooltips added on relevant widgets.
    • User warning when attempting to combine disease selection with environment samples.
  • Tests

    • Added tests and test data for environments, environment samples, and environment-aware dashboard behavior.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Aug 13, 2025

Walkthrough

Adds environment-sample support across API, backend, UI, and tests: new criteria field and i18n keys, facade/service environment aggregations, UI filter and data-provider branching, EPI curve restricted to humans, map/dashboard branching between human and environment samples, and test utilities for environment entities. Validation blocks disease+environment searches.

Changes

Cohort / File(s) Summary
API: Criteria & Facade
sormas-api/.../dashboard/SampleDashboardCriteria.java, sormas-api/.../dashboard/sample/SampleDashboardFacade.java
Adds environmentSampleMaterial field with getter and fluent setter to criteria; adds four facade methods for environment aggregations (counts by specimen condition, shipment status, test result type, and material).
API: i18n keys & resources
sormas-api/.../i18n/Descriptions.java, sormas-api/.../i18n/Strings.java, sormas-api/src/main/resources/descriptions.properties, sormas-api/src/main/resources/strings.properties
Adds new i18n keys (sampleDashboardHumans, headingSearchSample, messageSampleSearchWithDisease) and corresponding properties entries.
Backend: Facade EJB
sormas-backend/.../dashboard/sample/SampleDashboardFacadeEjb.java
Exposes four environment aggregation methods delegating to service; fixes internal delegation name.
Backend: Service
sormas-backend/.../dashboard/sample/SampleDashboardService.java
Implements environment aggregations (by specimen condition, shipment status, test result, material); adds grouped-query helper for EnvironmentSample; switches environment filtering to environmentSampleMaterial; renames/counts method variant.
UI: Core components & headers
sormas-ui/.../dashboard/diagram/AbstractEpiCurveComponent.java, sormas-ui/.../dashboard/surveillance/components/statistics/LaboratoryResultsStatisticsComponent.java
Adds constructor allowing description and info icon in headers; refactors header fields/layout and uses sampleDashboardHumans for descriptions.
UI: Data provider & view logic
sormas-ui/.../dashboard/sample/SampleDashboardDataProvider.java, sormas-ui/.../dashboard/sample/SampleDashboardView.java
Adds environmentSampleMaterial state, environment count maps and accessors; refreshData branches for human/environment/mixed, validates disease+environment, calls new facade methods; view merges human and environment counts for totals and charts.
UI: Filters & components
sormas-ui/.../dashboard/sample/SampleDashboardFilterLayout.java, sormas-ui/.../dashboard/sample/SampleEpiCurveComponent.java, sormas-ui/.../dashboard/sample/components/SampleDashboardMapComponent.java
Combines SampleMaterial and EnvironmentSampleMaterial in material filter; EPI curve generation limited to human samples; map loading branches with new helper predicates to fetch human and/or environment samples.
Tests & test utils
sormas-ui/src/test/.../AbstractBeanTest.java, sormas-ui/src/test/.../TestDataCreator.java, sormas-ui/src/test/.../SampleDashboardDataProviderTest.java
Adds environment facade accessor; test data creators for Environment and EnvironmentSample; extends tests to cover environment-material behavior and disease+environment validation.

Sequence Diagram(s)

sequenceDiagram
  participant UI as SampleDashboardView
  participant DP as SampleDashboardDataProvider
  participant Fac as SampleDashboardFacade (EJB)
  participant Svc as SampleDashboardService
  participant DB as Database

  UI->>DP: refreshData()
  DP->>DP: buildDashboardCriteria()
  alt disease + environmentMaterial
    DP-->>UI: show notification (headingSearchSample / messageSampleSearchWithDisease)
    DP-->>DP: abort refresh
  else environment-only
    DP->>Fac: getEnvironment*Counts(criteria)
    Fac->>Svc: delegate environment aggregations
    Svc->>DB: query/group EnvironmentSample
    Svc-->>Fac: return maps
    Fac-->>DP: return maps
  else human-only
    DP->>Fac: getSample*Counts(criteria)
    Fac->>Svc: delegate human aggregations
    Svc->>DB: query/group Sample
    Svc-->>Fac: return maps
    Fac-->>DP: return maps
  else mixed (no disease)
    DP->>Fac: getSample*Counts + getEnvironment*Counts
    Fac->>Svc: delegate both
    Svc->>DB: queries
    Svc-->>Fac: maps
    Fac-->>DP: maps
  end
  DP-->>UI: merged counts
  UI->>UI: update totals, charts, map
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • obinna-h-n

Poem

A whisker twitch, a dashboard new,
I hop through counts in human view;
For streams and air, I gently parse,
But disease with env? That’s off the charts!
I thump approval—maps and charts align. 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bugfix-13548-dashboard-samples-error

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🔭 Outside diff range comments (2)
sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/components/SampleDashboardMapComponent.java (2)

241-245: Wrong initial value for the environment samples checkbox

The checkbox value is tied to shouldShowEventParticipantSamples(), which is unrelated. It should reflect the current environment visibility toggle.

-			showEnvironmentSamplesCheckBox.setValue(shouldShowEventParticipantSamples());
+			showEnvironmentSamplesCheckBox.setValue(showEnvironmentalSamples);

259-264: Environment-only legend never shown

Early return when displayedHumanSamples is empty prevents the environment legend from rendering, even if showEnvironmentalSamples is true.

-		if (displayedHumanSamples.size() == 0) {
-			return Collections.emptyList();
-		}
+		if (displayedHumanSamples.size() == 0 && !showEnvironmentalSamples) {
+			return Collections.emptyList();
+		}
🧹 Nitpick comments (12)
sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/diagram/AbstractEpiCurveComponent.java (1)

25-27: Drop Component import after constructor cleanup

Component is only used for a temporary variable introduced in the new constructor; once simplified (see below), this import becomes unnecessary.

Apply after simplifying the constructor:

-import com.vaadin.ui.Component;
sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/components/statistics/LaboratoryResultsStatisticsComponent.java (1)

51-55: Nit: Prefer setIcon over HTML content for icons

Using HTML for icons is unnecessary; Vaadin Label supports setIcon. This avoids ContentMode.HTML and is more accessible.

For example:

-Label infoIcon = new Label(VaadinIcons.INFO_CIRCLE.getHtml(), ContentMode.HTML);
+Label infoIcon = new Label();
+infoIcon.setIcon(VaadinIcons.INFO_CIRCLE);
sormas-api/src/main/java/de/symeda/sormas/api/dashboard/SampleDashboardCriteria.java (1)

61-70: Clarify/enforce mutual exclusivity between sampleMaterial and environmentSampleMaterial

The criteria now has two material fields. Most downstream code likely expects one or the other to be set. Consider enforcing mutual exclusivity in the setters or document precedence.

Option A: Enforce exclusivity in setters (recommended for consistency):

// in SampleDashboardCriteria
public SampleDashboardCriteria sampleMaterial(SampleMaterial sampleMaterial) {
    this.sampleMaterial = sampleMaterial;
    if (sampleMaterial != null) {
        this.environmentSampleMaterial = null;
    }
    return self;
}

public SampleDashboardCriteria environmentSampleMaterial(EnvironmentSampleMaterial environmentSampleMaterial) {
    this.environmentSampleMaterial = environmentSampleMaterial;
    if (environmentSampleMaterial != null) {
        this.sampleMaterial = null;
    }
    return self;
}

Option B: Add Javadoc stating that only one of the two should be set and which one takes precedence if both are non-null.

sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/SampleDashboardFilterLayout.java (2)

116-123: Redundant sorting and potential de-dupe side effects with TreeSet comparator

You sort the stream and then collect into a TreeSet with the same comparator, effectively sorting twice. Also, de-duplication is based on toString, which may collapse identically captioned entries across enums (e.g., “Other”).

  • Remove the pre-collect sorted(...) step and rely on the TreeSet’s comparator.
  • Optionally, document that “Other” is intentionally de-duplicated and handled specially in the value-change listener.
-		Set<Enum<?>> combinedSampleMaterials = Stream
-				.concat(Stream.of(SampleMaterial.values()), Stream.of(EnvironmentSampleMaterial.values()))
-				.sorted(Comparator.comparing(Enum::toString))
-				.collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Enum::toString))));
+		Set<Enum<?>> combinedSampleMaterials = Stream
+				.concat(Stream.of(SampleMaterial.values()), Stream.of(EnvironmentSampleMaterial.values()))
+				.collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Enum::toString))));

127-140: Listener logic OK; minor robustness/readability improvements

  • Cache the selected value to avoid repeated e.getProperty().getValue().
  • The comment can be clarified.
-		// In Other as the selected sample, all samples should select.
-		if (e.getProperty().getValue() == SampleMaterial.OTHER || e.getProperty().getValue() == EnvironmentSampleMaterial.OTHER) {
+		// Selecting "Other" should select both human and environment "Other"
+		Object value = e.getProperty().getValue();
+		if (value == SampleMaterial.OTHER || value == EnvironmentSampleMaterial.OTHER) {
 			dashboardDataProvider.setEnvironmentSampleMaterial(EnvironmentSampleMaterial.OTHER);
 			dashboardDataProvider.setSampleMaterial(SampleMaterial.OTHER);
-		} else if (e.getProperty().getValue() instanceof EnvironmentSampleMaterial) {
-			dashboardDataProvider.setEnvironmentSampleMaterial((EnvironmentSampleMaterial) e.getProperty().getValue());
+		} else if (value instanceof EnvironmentSampleMaterial) {
+			dashboardDataProvider.setEnvironmentSampleMaterial((EnvironmentSampleMaterial) value);
 			dashboardDataProvider.setSampleMaterial(null);
-		} else if (e.getProperty().getValue() instanceof SampleMaterial) {
+		} else if (value instanceof SampleMaterial) {
 			dashboardDataProvider.setEnvironmentSampleMaterial(null);
-			dashboardDataProvider.setSampleMaterial((SampleMaterial) e.getProperty().getValue());
+			dashboardDataProvider.setSampleMaterial((SampleMaterial) value);
 		} else {
 			dashboardDataProvider.setEnvironmentSampleMaterial(null);
 			dashboardDataProvider.setSampleMaterial(null);
 		}
sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/SampleEpiCurveComponent.java (2)

70-99: Epi curve correctly gated to human samples; consider UX for environment mode

The guard prevents computing the curve when environmentSampleMaterial is set. Currently this silently renders all-zero series. Consider hiding the chart or showing a short description that the EPI curve is only available for human samples to avoid confusing users with flat zero lines.


75-83: Potential performance concern: N remote calls inside loop

Fetching counts per date inside the loop can be chatty over EJB. If this becomes a bottleneck, consider a backend endpoint that aggregates by date bucket (day/week/month) in one call and returns a time series.

sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/components/SampleDashboardMapComponent.java (1)

83-101: Helper naming/docs OK; logic aligns with usage

The empty criteria helpers are straightforward and used correctly in the branching. Minor nit: the Javadoc for “WithoutDisease” vs “WithDisease” could be crisper, but not blocking.

If you want, I can tighten the Javadoc wording for clarity.

sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardService.java (2)

551-552: Nit: unnecessary cb.or with single predicate

Wrapping a single cb.between(...) inside cb.or(...) is redundant. Simplify for readability.

-			case ASSOCIATED_ENTITY_REPORT_DATE:
-				dateFilter = cb.or(cb.between(joins.getEnvironment().get(Environment.REPORT_DATE), dateFrom, dateTo));
+			case ASSOCIATED_ENTITY_REPORT_DATE:
+				dateFilter = cb.between(joins.getEnvironment().get(Environment.REPORT_DATE), dateFrom, dateTo);

576-581: Comment refers to “cases” in environment filter

The comment “Exclude deleted cases” should say “Exclude deleted samples” to match the context.

-			// Exclude deleted cases. Archived cases should stay included
+			// Exclude deleted samples. Archived samples should stay included
sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/SampleDashboardDataProvider.java (2)

210-212: Naming nit: pluralize to reflect Map return type

getEnvironmentSampleCount returns a Map; consider renaming to getEnvironmentSampleCounts for clarity and consistency with other getters.

If you agree, I can generate a follow-up diff (including call sites).


38-56: Reduce duplication by extracting clear/reset helpers

There’s repeated boilerplate to clear either human or environment maps. Consider small private helpers:

  • clearHumanCounts()
  • clearEnvironmentCounts()

This improves readability and reduces mistakes when adding/removing metrics.

Example (outside changed lines):

private void clearHumanCounts() {
  sampleCountsByResultType = Maps.newHashMap();
  sampleCountsByPurpose = Maps.newHashMap();
  sampleCountsBySpecimenCondition = Maps.newHashMap();
  sampleCountsByShipmentStatus = Maps.newHashMap();
  testResultCountsByResultType = Maps.newHashMap();
}

private void clearEnvironmentCounts() {
  envSampleCountsBySpecimenCondition = Maps.newHashMap();
  envSampleCountsByShipmentStatus = Maps.newHashMap();
  envTestResultCountsByResultType = Maps.newHashMap();
  envSampleCount = Maps.newHashMap();
}

Then use these in refreshData branches and on the early return.

Also applies to: 60-111

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 738a87d and 4e3d5e3.

📒 Files selected for processing (18)
  • sormas-api/src/main/java/de/symeda/sormas/api/dashboard/SampleDashboardCriteria.java (2 hunks)
  • sormas-api/src/main/java/de/symeda/sormas/api/dashboard/sample/SampleDashboardFacade.java (2 hunks)
  • sormas-api/src/main/java/de/symeda/sormas/api/i18n/Descriptions.java (1 hunks)
  • sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java (2 hunks)
  • sormas-api/src/main/resources/descriptions.properties (1 hunks)
  • sormas-api/src/main/resources/strings.properties (2 hunks)
  • sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardFacadeEjb.java (2 hunks)
  • sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardService.java (5 hunks)
  • sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/diagram/AbstractEpiCurveComponent.java (4 hunks)
  • sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/SampleDashboardDataProvider.java (5 hunks)
  • sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/SampleDashboardFilterLayout.java (2 hunks)
  • sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/SampleDashboardView.java (4 hunks)
  • sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/SampleEpiCurveComponent.java (3 hunks)
  • sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/components/SampleDashboardMapComponent.java (2 hunks)
  • sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/components/statistics/LaboratoryResultsStatisticsComponent.java (2 hunks)
  • sormas-ui/src/test/java/de/symeda/sormas/backend/AbstractBeanTest.java (2 hunks)
  • sormas-ui/src/test/java/de/symeda/sormas/backend/TestDataCreator.java (3 hunks)
  • sormas-ui/src/test/java/de/symeda/sormas/ui/dashboard/sample/SampleDashboardDataProviderTest.java (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (5)
sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/SampleDashboardDataProvider.java (3)
sormas-api/src/main/java/de/symeda/sormas/api/FacadeProvider.java (1)
  • FacadeProvider (127-594)
sormas-api/src/main/java/de/symeda/sormas/api/dashboard/SampleDashboardCriteria.java (1)
  • SampleDashboardCriteria (22-71)
sormas-api/src/main/java/de/symeda/sormas/api/i18n/I18nProperties.java (1)
  • I18nProperties (39-536)
sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/diagram/AbstractEpiCurveComponent.java (2)
sormas-ui/src/main/java/de/symeda/sormas/ui/utils/CssStyles.java (1)
  • CssStyles (26-535)
sormas-api/src/main/java/de/symeda/sormas/api/i18n/I18nProperties.java (1)
  • I18nProperties (39-536)
sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardService.java (2)
sormas-backend/src/main/java/de/symeda/sormas/backend/common/CriteriaBuilderHelper.java (1)
  • CriteriaBuilderHelper (30-230)
sormas-backend/src/main/java/de/symeda/sormas/backend/util/QueryHelper.java (1)
  • QueryHelper (22-353)
sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/SampleEpiCurveComponent.java (1)
sormas-api/src/main/java/de/symeda/sormas/api/FacadeProvider.java (1)
  • FacadeProvider (127-594)
sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/components/statistics/LaboratoryResultsStatisticsComponent.java (2)
sormas-api/src/main/java/de/symeda/sormas/api/i18n/I18nProperties.java (1)
  • I18nProperties (39-536)
sormas-ui/src/main/java/de/symeda/sormas/ui/utils/CssStyles.java (1)
  • CssStyles (26-535)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: android app test (26)
  • GitHub Check: android app test (28)
  • GitHub Check: android app test (27)
  • GitHub Check: Lint Code Base
  • GitHub Check: SORMAS CI
🔇 Additional comments (26)
sormas-api/src/main/java/de/symeda/sormas/api/i18n/Descriptions.java (1)

144-145: New i18n description key is consistent and well-placed

The new Descriptions.sampleDashboardHumans constant follows the established naming and placement conventions, and the backing property exists in descriptions.properties.

sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java (1)

830-831: i18n constants added with correct prefixes and intent

  • headingSearchSample and messageSampleSearchWithDisease align with existing heading*/message* patterns.
  • Corresponding entries exist in strings.properties with clear user-facing text.

No further action from API side.

Also applies to: 1561-1562

sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/diagram/AbstractEpiCurveComponent.java (3)

56-57: Promoting header widgets to fields enables post-construction customization

Making infoIcon, epiCurveHeaderLayout and expandEpiCurveButton fields is the right move to allow header updates from the overloaded constructor.

Also applies to: 62-64


116-116: Header layout switched to field – good for later mutation

Using epiCurveHeaderLayout as a field instead of a local variable is required for the new constructor logic to modify the header in place.


130-131: Storing expand button in a field enables toggling after header rebuild

Assigning expandEpiCurveButton to a field is necessary for removal/re-adding when customizing the header. Ensure this remains assigned only once.

sormas-api/src/main/resources/descriptions.properties (1)

237-238: New description text is clear and matches the new key

The description is concise and matches the usage intent on the sample dashboard. No issues.

sormas-api/src/main/resources/strings.properties (1)

519-520: New strings read well and align with UI flows

  • headingSearchSample: Clear title for the notification.
  • messageSampleSearchWithDisease: Communicates the constraint precisely.

Ensure these are wired where the validation is triggered so users get immediate feedback.

Also applies to: 1265-1266

sormas-ui/src/test/java/de/symeda/sormas/backend/AbstractBeanTest.java (2)

36-38: LGTM: Added environment sample facade imports fit the existing test pattern

The new imports align with how other facades are wired in tests and look correct.


1007-1009: Provide EnvironmentSampleFacade via Local EJB — consistent with the rest

Accessor mirrors the existing facade getters (returning the API type while resolving the Local EJB). Good addition.

sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/components/statistics/LaboratoryResultsStatisticsComponent.java (1)

5-7: LGTM: Imports required for title layout and info icon

The VaadinIcons/ContentMode/HorizontalLayout and Descriptions imports are appropriate for the new UI elements.

Also applies to: 11-11

sormas-ui/src/test/java/de/symeda/sormas/ui/dashboard/sample/SampleDashboardDataProviderTest.java (4)

20-25: Imports for environment entities look correct

The added imports align with the new environment test setup and are used below. No issues.


73-77: Correct precedence when environmentSampleMaterial is set

Asserting that environmentSampleMaterial takes precedence when sampleMaterial is null is correct and future-proofs the criteria builder behavior.


108-114: Environment test data setup is fine

Environment, lab facility, and environment sample creation is valid. This provides a solid basis for the following assertions.


115-124: Good: Blocking environment counts when disease filter is set

Verifies that disease filters do not mix with environment samples, and that all counts (both environment and human) are empty under this invalid criterion. This matches the intended behavior.

sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/SampleEpiCurveComponent.java (1)

43-45: UI description switch to human samples is appropriate

Forwarding Descriptions.sampleDashboardHumans to the superclass clarifies the chart’s scope. No issues here.

sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/SampleDashboardView.java (2)

176-189: Merging environment counts into human is correct and null-safe

Use of Optional and Map.merge for specimen condition and shipment status looks good and consistent.


105-113: Descriptions updated to human-focused text

Switching to Descriptions.sampleDashboardHumans for lab results and purpose sections is consistent with the EPI curve gating and overall UX.

sormas-ui/src/test/java/de/symeda/sormas/backend/TestDataCreator.java (1)

1405-1435: Environment creation helper looks good

Helper sets required fields, applies optional config, copies jurisdiction from rdcf, and persists. Solid addition for tests.

sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardFacadeEjb.java (1)

90-109: Facade extended with environment-specific aggregations – looks consistent

The new methods delegate cleanly to SampleDashboardService and expose the required environment counts. Naming and types are consistent with the API.

sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/components/SampleDashboardMapComponent.java (2)

120-128: Ambiguous semantics for isSampleCriteriaForOther

Currently, selecting SampleMaterial.OTHER or EnvironmentSampleMaterial.OTHER triggers loading both human and environment samples. If “OTHER” is intended to mean “material equals OTHER” (not “all except”), loading both sets unconditionally may be misleading.

Consider either:

  • Restricting to the selected domain (human vs environment), or
  • Reclassifying this case into the mixed/both branch only when both materials are explicitly set to OTHER.

Do we intend “OTHER” to broaden to both domains, or to filter within the selected domain only? If the latter, adjust the branching accordingly.


63-65: Inconsistent permission gating for environment samples

  • Initialization uses UiUtil.permitted(FeatureType.ENVIRONMENT_MANAGEMENT, UserRight.ENVIRONMENT_SAMPLE_VIEW).
  • Layer checkbox uses UiUtil.permitted(UserRight.ENVIRONMENT_VIEW).

These may target different rights/feature flags. Align them to the intended authorization policy so that checkbox visibility and initial state remain consistent.

Would you confirm which right(s) should guard environment sample visibility? I can align both checks and add a utility method if needed.

Also applies to: 240-255

sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardService.java (3)

225-242: Environment shipment status aggregation mirrors human; LGTM

Consistent query structure (left join not needed here) and the merge function in toMap protects against duplicates. Looks good.


298-311: Counts by environment material: good addition

Using createEnvironmentSampleFilter and grouping by EnvironmentSample.SAMPLE_MATERIAL is appropriate. No issues.


572-574: Correct switch to environment material in filter

Changing the filter from criteria.getSampleMaterial() to criteria.getEnvironmentSampleMaterial() is correct and necessary for environment domain filtering.

sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/SampleDashboardDataProvider.java (2)

70-111: Branching and facade calls look consistent with backend; good

Human-only, environment-only, and mixed branches are handled coherently. Environment domain correctly suppressed when disease is set.


120-121: Criteria builder correctly propagates environment material

Including environmentSampleMaterial alongside sampleMaterial is correct.

showEnvironmentalSamples ? FacadeProvider.getSampleDashboardFacade().getEnvironmentalSamplesForMap(criteria) : Collections.emptyList();
}

List<LeafletMarker> markers = new ArrayList<>(environmentSamples.size() + environmentSamples.size());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Initial capacity uses environment size twice

ArrayList is initialized with environmentSamples.size() + environmentSamples.size(). This should include humanSamples.size().

-		List<LeafletMarker> markers = new ArrayList<>(environmentSamples.size() + environmentSamples.size());
+		List<LeafletMarker> markers = new ArrayList<>(humanSamples.size() + environmentSamples.size());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
List<LeafletMarker> markers = new ArrayList<>(environmentSamples.size() + environmentSamples.size());
List<LeafletMarker> markers = new ArrayList<>(humanSamples.size() + environmentSamples.size());
🤖 Prompt for AI Agents
In
sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/components/SampleDashboardMapComponent.java
around line 156, the ArrayList initial capacity incorrectly sums
environmentSamples.size() twice; change the second operand to
humanSamples.size() so the initial capacity is humanSamples.size() +
environmentSamples.size(), ensuring the list is sized to hold both sample sets.

Comment on lines +84 to +91
Map<PathogenTestResultType, Long> sampleCounts = FacadeProvider.getSampleDashboardFacade().getSampleCountsByResultType(criteria);

Long positiveCount = sampleCounts.get(PathogenTestResultType.POSITIVE);
Long negativeCount = sampleCounts.get(PathogenTestResultType.NEGATIVE);
Long pendingCount = sampleCounts.get(PathogenTestResultType.PENDING);
Long indeterminateCount = sampleCounts.get((PathogenTestResultType.INDETERMINATE));
Long notDoneCount = sampleCounts.get(PathogenTestResultType.NOT_DONE);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Defensive null-handling for facade result to avoid NPEs

getSampleCountsByResultType may return null in edge cases; guard against it to prevent NPEs.

Apply this diff:

-                    Map<PathogenTestResultType, Long> sampleCounts = FacadeProvider.getSampleDashboardFacade().getSampleCountsByResultType(criteria);
+                    Map<PathogenTestResultType, Long> sampleCounts = FacadeProvider.getSampleDashboardFacade().getSampleCountsByResultType(criteria);
+                    if (sampleCounts == null) {
+                        sampleCounts = java.util.Collections.emptyMap();
+                    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Map<PathogenTestResultType, Long> sampleCounts = FacadeProvider.getSampleDashboardFacade().getSampleCountsByResultType(criteria);
Long positiveCount = sampleCounts.get(PathogenTestResultType.POSITIVE);
Long negativeCount = sampleCounts.get(PathogenTestResultType.NEGATIVE);
Long pendingCount = sampleCounts.get(PathogenTestResultType.PENDING);
Long indeterminateCount = sampleCounts.get((PathogenTestResultType.INDETERMINATE));
Long notDoneCount = sampleCounts.get(PathogenTestResultType.NOT_DONE);
Map<PathogenTestResultType, Long> sampleCounts = FacadeProvider.getSampleDashboardFacade().getSampleCountsByResultType(criteria);
if (sampleCounts == null) {
sampleCounts = java.util.Collections.emptyMap();
}
Long positiveCount = sampleCounts.get(PathogenTestResultType.POSITIVE);
Long negativeCount = sampleCounts.get(PathogenTestResultType.NEGATIVE);
Long pendingCount = sampleCounts.get(PathogenTestResultType.PENDING);
Long indeterminateCount = sampleCounts.get(PathogenTestResultType.INDETERMINATE);
Long notDoneCount = sampleCounts.get(PathogenTestResultType.NOT_DONE);
🤖 Prompt for AI Agents
In
sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/SampleEpiCurveComponent.java
around lines 84 to 91, the Map returned by getSampleCountsByResultType may be
null which can cause NPEs when calling get(...); guard against null by replacing
a null map with Collections.emptyMap() (or new HashMap<>()), then fetch each
count using map.getOrDefault(PathogenTestResultType.X, 0L) (or check for null
and default to 0L) for POSITIVE, NEGATIVE, PENDING, INDETERMINATE and NOT_DONE
so all counts are non-null longs.

Comment on lines +40 to 56
HorizontalLayout titleLayout = new HorizontalLayout();
titleLayout.setMargin(false);
titleLayout.setSpacing(false);
addComponent(titleLayout);

if (subtitleCaption != null) {
Label subTitleLabel = new Label(I18nProperties.getCaption(subtitleCaption));
CssStyles.style(subTitleLabel, CssStyles.H3, CssStyles.VSPACE_TOP_5);
addComponent(subTitleLabel);
titleLayout.addComponent(subTitleLabel);
}
if (description != null) {
Label infoIcon = new Label(VaadinIcons.INFO_CIRCLE.getHtml(), ContentMode.HTML);
infoIcon.setDescription(I18nProperties.getDescription(Descriptions.sampleDashboardHumans));
infoIcon.addStyleName(CssStyles.HSPACE_LEFT_4);
infoIcon.addStyleNames(CssStyles.H3);
titleLayout.addComponent(infoIcon);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid duplicate/wrongly gated info icon; use showInfoIcon and don’t pass info to super

Current logic can render two info icons:

  • One possibly added by the super constructor (third arg when showInfoIcon is true).
  • Another added here when description != null (regardless of showInfoIcon).

Gate the icon with showInfoIcon and stop passing the info text to super to avoid duplication.

Apply:

-		super(titleCaption, description, showInfoIcon ? I18nProperties.getString(Strings.infoDashboardFinalLaboratoryResult) : "");
+		super(titleCaption, description, "");

@@
-		if (description != null) {
+		if (showInfoIcon) {
 			Label infoIcon = new Label(VaadinIcons.INFO_CIRCLE.getHtml(), ContentMode.HTML);
 			infoIcon.setDescription(I18nProperties.getDescription(Descriptions.sampleDashboardHumans));
 			infoIcon.addStyleName(CssStyles.HSPACE_LEFT_4);
 			infoIcon.addStyleNames(CssStyles.H3);
 			titleLayout.addComponent(infoIcon);
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
HorizontalLayout titleLayout = new HorizontalLayout();
titleLayout.setMargin(false);
titleLayout.setSpacing(false);
addComponent(titleLayout);
if (subtitleCaption != null) {
Label subTitleLabel = new Label(I18nProperties.getCaption(subtitleCaption));
CssStyles.style(subTitleLabel, CssStyles.H3, CssStyles.VSPACE_TOP_5);
addComponent(subTitleLabel);
titleLayout.addComponent(subTitleLabel);
}
if (description != null) {
Label infoIcon = new Label(VaadinIcons.INFO_CIRCLE.getHtml(), ContentMode.HTML);
infoIcon.setDescription(I18nProperties.getDescription(Descriptions.sampleDashboardHumans));
infoIcon.addStyleName(CssStyles.HSPACE_LEFT_4);
infoIcon.addStyleNames(CssStyles.H3);
titleLayout.addComponent(infoIcon);
}
// Change the super call to stop passing the info‐text
- super(titleCaption, description, showInfoIcon ? I18nProperties.getString(Strings.infoDashboardFinalLaboratoryResult) : "");
+ super(titleCaption, description, "");
HorizontalLayout titleLayout = new HorizontalLayout();
titleLayout.setMargin(false);
titleLayout.setSpacing(false);
addComponent(titleLayout);
if (subtitleCaption != null) {
Label subTitleLabel = new Label(I18nProperties.getCaption(subtitleCaption));
CssStyles.style(subTitleLabel, CssStyles.H3, CssStyles.VSPACE_TOP_5);
titleLayout.addComponent(subTitleLabel);
}
- if (description != null) {
+ if (showInfoIcon) {
Label infoIcon = new Label(VaadinIcons.INFO_CIRCLE.getHtml(), ContentMode.HTML);
infoIcon.setDescription(I18nProperties.getDescription(Descriptions.sampleDashboardHumans));
infoIcon.addStyleName(CssStyles.HSPACE_LEFT_4);
infoIcon.addStyleNames(CssStyles.H3);
titleLayout.addComponent(infoIcon);
}
🤖 Prompt for AI Agents
In
sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/components/statistics/LaboratoryResultsStatisticsComponent.java
around lines 40 to 56, the info icon can be rendered twice because the
superclass may add it when showInfoIcon is true and this constructor always adds
an info icon when description != null; remove the info text argument passed to
super so the superclass does not auto-add the icon, and wrap the local info-icon
creation and addition with a check for showInfoIcon (i.e., only create/add the
Label(VaadinIcons.INFO_CIRCLE...) when showInfoIcon is true and description !=
null) so the icon appears only once and only when explicitly requested.

Comment on lines +1437 to +1455
public EnvironmentSampleDto createEnvironmentSample(
EnvironmentReferenceDto environment,
UserReferenceDto reportingUser,
RDCF rdcf,
FacilityReferenceDto lab,
@Nullable Consumer<EnvironmentSampleDto> extraConfig) {
EnvironmentSampleDto sample = EnvironmentSampleDto.build(environment, reportingUser);
sample.setSampleMaterial(EnvironmentSampleMaterial.WATER);
sample.getLocation().setRegion(rdcf.region);
sample.getLocation().setLatitude(1.0);
sample.getLocation().setLongitude(1.0);
sample.getLocation().setDistrict(rdcf.district);
sample.setLaboratory(lab);
if (extraConfig != null) {
extraConfig.accept(sample);
}

return beanTest.getEnvironmentSampleFacade().save(sample);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Guard against null RDCF to prevent NPEs when populating location

createEnvironmentSample unconditionally dereferences rdcf. Either mark rdcf as @NotNull or guard against null like other helpers do. Also keep latitude/longitude assignment outside the guard.

Apply this diff:

-        sample.getLocation().setRegion(rdcf.region);
-        sample.getLocation().setLatitude(1.0);
-        sample.getLocation().setLongitude(1.0);
-        sample.getLocation().setDistrict(rdcf.district);
+        if (rdcf != null) {
+            sample.getLocation().setRegion(rdcf.region);
+            sample.getLocation().setDistrict(rdcf.district);
+        }
+        sample.getLocation().setLatitude(1.0);
+        sample.getLocation().setLongitude(1.0);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public EnvironmentSampleDto createEnvironmentSample(
EnvironmentReferenceDto environment,
UserReferenceDto reportingUser,
RDCF rdcf,
FacilityReferenceDto lab,
@Nullable Consumer<EnvironmentSampleDto> extraConfig) {
EnvironmentSampleDto sample = EnvironmentSampleDto.build(environment, reportingUser);
sample.setSampleMaterial(EnvironmentSampleMaterial.WATER);
sample.getLocation().setRegion(rdcf.region);
sample.getLocation().setLatitude(1.0);
sample.getLocation().setLongitude(1.0);
sample.getLocation().setDistrict(rdcf.district);
sample.setLaboratory(lab);
if (extraConfig != null) {
extraConfig.accept(sample);
}
return beanTest.getEnvironmentSampleFacade().save(sample);
}
public EnvironmentSampleDto createEnvironmentSample(
EnvironmentReferenceDto environment,
UserReferenceDto reportingUser,
RDCF rdcf,
FacilityReferenceDto lab,
@Nullable Consumer<EnvironmentSampleDto> extraConfig) {
EnvironmentSampleDto sample = EnvironmentSampleDto.build(environment, reportingUser);
sample.setSampleMaterial(EnvironmentSampleMaterial.WATER);
if (rdcf != null) {
sample.getLocation().setRegion(rdcf.region);
sample.getLocation().setDistrict(rdcf.district);
}
sample.getLocation().setLatitude(1.0);
sample.getLocation().setLongitude(1.0);
sample.setLaboratory(lab);
if (extraConfig != null) {
extraConfig.accept(sample);
}
return beanTest.getEnvironmentSampleFacade().save(sample);
}
🤖 Prompt for AI Agents
In sormas-ui/src/test/java/de/symeda/sormas/backend/TestDataCreator.java around
lines 1437-1455, the method createEnvironmentSample dereferences rdcf
unconditionally which can cause an NPE; either annotate the rdcf parameter as
@NotNull or (preferred) add a null guard: keep the latitude and longitude
assignments outside the guard, and wrap only the rdcf-dependent calls
(sample.getLocation().setRegion(...); sample.getLocation().setDistrict(...);) in
an if (rdcf != null) { ... } block so the method is safe when rdcf is null.

@sormas-vitagroup
Copy link
Copy Markdown
Contributor

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (1)
sormas-ui/src/test/java/de/symeda/sormas/ui/dashboard/sample/SampleDashboardDataProviderTest.java (1)

82-82: Ensure Maven modules compile with Java 11 for var support

I searched all POMs and found no <java.version>, <maven.compiler.source>, <maven.compiler.target>, or <release> settings—Maven defaults to Java 1.5/1.6. Meanwhile, the Gradle modules target Java 11 (source/targetCompatibility). To avoid CI failures on the new var in SampleDashboardDataProviderTest.java, please add or update your Maven compiler configuration (in sormas-ui/pom.xml or the parent POM):

• Location to update:

  • sormas-ui/pom.xml (or its parent POM)

• Example snippet:

<properties>
  <java.version>11</java.version>
</properties>

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.8.1</version>
      <configuration>
        <release>${java.version}</release>
      </configuration>
    </plugin>
  </plugins>
</build>

If you already manage Java toolchains in an external parent POM, confirm it sets the release/source/target to at least 11 for all modules (including tests).

♻️ Duplicate comments (4)
sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/SampleDashboardView.java (1)

163-171: Fix compile-time type mismatch and NPE risk when summing totals

Stream.concat over entry sets with different key types won’t compile, and getEnvironmentSampleCount() may be null. Compute human and environment totals independently and guard the environment map.

Apply this diff:

-        // combining sample counts of humans and environment samples
-        long totalSamples = Stream.concat(dataProvider.getSampleCountsByResultType().entrySet().stream(), dataProvider.getEnvironmentSampleCount().entrySet()
-                        .stream())
-                .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue(), Long::sum))
-                .values()
-                .stream()
-                .mapToLong(Long::longValue).sum();
+        // Combine totals from human and environment samples (different key types)
+        long humanTotal = dataProvider.getSampleCountsByResultType()
+            .values()
+            .stream()
+            .mapToLong(Long::longValue)
+            .sum();
+        long environmentTotal = Optional.ofNullable(dataProvider.getEnvironmentSampleCount())
+            .map(m -> m.values().stream().mapToLong(Long::longValue).sum())
+            .orElse(0L);
+        long totalSamples = humanTotal + environmentTotal;
 
         heading.updateTotalLabel(String.valueOf(totalSamples));
         //Final laboratory results only for human samples.
sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/components/SampleDashboardMapComponent.java (2)

154-156: Handle mixed criteria: load both human and environment samples

With both human and environment materials selected, the current else branch loads nothing. Fetch both sets to mirror DataProvider behavior; gate environment by showEnvironmentalSamples.

Apply this diff:

-        } else{
-            // In the case of mixed sample criteria, we want to show both human and environment samples; for now this is not supported
-        }
+        } else {
+            // Mixed selection: show both human and environment samples (environment gated by permission/checkbox)
+            humanSamples = FacadeProvider.getSampleDashboardFacade().getSamplesForMap(criteria, displayedHumanSamples);
+            environmentSamples = showEnvironmentalSamples
+                ? FacadeProvider.getSampleDashboardFacade().getEnvironmentalSamplesForMap(criteria)
+                : Collections.emptyList();
+        }

158-158: Fix initial capacity: sum human + environment sizes

You add environmentSamples.size() twice. Use humanSamples.size() + environmentSamples.size() to prevent unnecessary resizes.

Apply this diff:

-        List<LeafletMarker> markers = new ArrayList<>(environmentSamples.size() + environmentSamples.size());
+        List<LeafletMarker> markers = new ArrayList<>(humanSamples.size() + environmentSamples.size());
sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardService.java (1)

287-291: Thanks for addressing prior NPE risk with null test results

Filtering out tuples with a null result ensures toMap doesn’t NPE and aligns with the earlier review feedback.

🧹 Nitpick comments (6)
sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/SampleDashboardView.java (1)

176-182: Optional: Deduplicate map-merging with a small helper

You repeat the same null-safe merge pattern twice. Consider extracting a generic helper to reduce duplication and future mistakes.

Example helper (place it as a private static method in this class):

private static <K> Map<K, Long> mergedCounts(Map<K, Long> human, Map<K, Long> environment) {
    Map<K, Long> result = new HashMap<>(human);
    Optional.ofNullable(environment).ifPresent(m -> m.forEach((k, v) -> result.merge(k, v, Long::sum)));
    return result;
}

Then replace the current blocks with:

-        Map<SpecimenCondition, Long> resultSpecimenConditionMap = new HashMap<>(dataProvider.getSampleCountsBySpecimenCondition());
-        Optional.ofNullable(dataProvider.getEnvSampleCountsBySpecimenCondition()).ifPresent(m -> m.forEach((condition, count) -> {
-            resultSpecimenConditionMap.merge(condition, count, Long::sum);
-        }));
-        countsBySpecimenCondition.update(resultSpecimenConditionMap);
+        countsBySpecimenCondition.update(
+            mergedCounts(dataProvider.getSampleCountsBySpecimenCondition(), dataProvider.getEnvSampleCountsBySpecimenCondition())
+        );
-        Map<SampleShipmentStatus, Long> resultShipmentStatusMap = new HashMap<>(dataProvider.getSampleCountsByShipmentStatus());
-        Optional.ofNullable(dataProvider.getEnvSampleCountsByShipmentStatus()).ifPresent(m -> m.forEach((status, count) -> {
-            resultShipmentStatusMap.merge(status, count, Long::sum);
-        }));
-        countsByShipmentStatus.update(resultShipmentStatusMap);
+        countsByShipmentStatus.update(
+            mergedCounts(dataProvider.getSampleCountsByShipmentStatus(), dataProvider.getEnvSampleCountsByShipmentStatus())
+        );

Also applies to: 183-189

sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/components/SampleDashboardMapComponent.java (2)

83-101: Nit: Javadoc wording is inaccurate

The criteria object isn’t “null”; you’re checking that no sample materials are selected. Consider tightening the doc to avoid confusion.

Apply this diff to both comments:

-    /**
-     * Checks if the sample criteria is null, meaning that no human sample material or environment sample material is selected.
-     * @param criteria
-     * @return boolean
-     */
+    /**
+     * Checks whether no human or environment sample material is selected and no disease is set.
+     */

And for the “with disease” variant:

-    /**
-     * Checks if the sample criteria is null, meaning that no human sample material or environment sample material is selected.
-     *
-     * @param criteria
-     * @return boolean
-     */
+    /**
+     * Checks whether no human or environment sample material is selected and a disease is set.
+     */

120-129: Nit: Incomplete Javadoc sentence

The sentence ends abruptly and doesn’t describe the rule clearly.

Apply this diff:

-    /**
-     * Checks if the sample criteria is for other sample materials, meaning that all other(Human+Environment) sample materials are selected except,
-     *
-     * @param criteria
-     * @return
-     */
+    /**
+     * Checks whether the criteria targets “OTHER” in either human or environment sample materials.
+     */
sormas-ui/src/test/java/de/symeda/sormas/ui/dashboard/sample/SampleDashboardDataProviderTest.java (2)

55-58: Rename variable for clarity (typo: toDateDate).

Minor readability nit: Rename toDateDate to toDate.

-        Date toDateDate = DateHelper.getDateZero(2023, 2, 25);
+        Date toDate = DateHelper.getDateZero(2023, 2, 25);
@@
-        dataProvider.setToDate(toDateDate);
+        dataProvider.setToDate(toDate);

72-78: Add a targeted test for mutually exclusive materials (human vs environment).

Good check that environmentSampleMaterial is passed and sampleMaterial remains null. To prevent regressions, consider adding a dedicated test to assert that both materials cannot be set simultaneously (i.e., provider or criteria enforces mutual exclusivity).

Example test to add:

@Test
public void testBuildDashboardCriteria_materialsAreMutuallyExclusive() {
    dataProvider.setSampleMaterial(SampleMaterial.CRUST);
    dataProvider.setEnvironmentSampleMaterial(EnvironmentSampleMaterial.AIR);

    // Depending on intended behavior, assert one of the following:
    // 1) Environment material wins and sample material is cleared:
    SampleDashboardCriteria criteria = dataProvider.buildDashboardCriteriaWithDates();
    Assertions.assertNull(criteria.getSampleMaterial());
    Assertions.assertEquals(EnvironmentSampleMaterial.AIR, criteria.getEnvironmentSampleMaterial());

    // OR 2) Provider rejects the invalid combo:
    // Assertions.assertThrows(IllegalArgumentException.class, () -> dataProvider.buildDashboardCriteriaWithDates());
}
sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardService.java (1)

267-272: Javadoc mismatch: method counts tests, not samples

The query groups by PathogenTest.TEST_RESULT and counts PathogenTest rows, i.e., tests-by-result-type, not samples.

Update the Javadoc:

-	/**
-	 * Returns a map of counts of environment samples by result type.
-	 *
-	 * @param dashboardCriteria
-	 * @return
-	 */
+	/**
+	 * Returns a map of counts of pathogen tests by result type for environment samples.
+	 * Groups by PathogenTest.TEST_RESULT and counts PathogenTest records.
+	 *
+	 * @param dashboardCriteria criteria to filter environment samples/tests
+	 * @return map keyed by PathogenTestResultType with the number of tests
+	 */
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e3d5e3 and 74f3cda.

📒 Files selected for processing (9)
  • sormas-api/src/main/java/de/symeda/sormas/api/dashboard/sample/SampleDashboardFacade.java (2 hunks)
  • sormas-api/src/main/resources/strings.properties (2 hunks)
  • sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardFacadeEjb.java (2 hunks)
  • sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardService.java (5 hunks)
  • sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/diagram/AbstractEpiCurveComponent.java (4 hunks)
  • sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/SampleDashboardDataProvider.java (5 hunks)
  • sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/SampleDashboardView.java (4 hunks)
  • sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/components/SampleDashboardMapComponent.java (2 hunks)
  • sormas-ui/src/test/java/de/symeda/sormas/ui/dashboard/sample/SampleDashboardDataProviderTest.java (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • sormas-api/src/main/resources/strings.properties
🚧 Files skipped from review as they are similar to previous changes (4)
  • sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardFacadeEjb.java
  • sormas-api/src/main/java/de/symeda/sormas/api/dashboard/sample/SampleDashboardFacade.java
  • sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/SampleDashboardDataProvider.java
  • sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/diagram/AbstractEpiCurveComponent.java
🧰 Additional context used
🧬 Code Graph Analysis (2)
sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/components/SampleDashboardMapComponent.java (1)
sormas-api/src/main/java/de/symeda/sormas/api/FacadeProvider.java (1)
  • FacadeProvider (127-594)
sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardService.java (2)
sormas-backend/src/main/java/de/symeda/sormas/backend/common/CriteriaBuilderHelper.java (1)
  • CriteriaBuilderHelper (30-230)
sormas-backend/src/main/java/de/symeda/sormas/backend/util/QueryHelper.java (1)
  • QueryHelper (22-353)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: android app test (27)
  • GitHub Check: Lint Code Base
  • GitHub Check: android app test (28)
  • GitHub Check: android app test (26)
  • GitHub Check: SORMAS CI
🔇 Additional comments (12)
sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/sample/SampleDashboardView.java (3)

106-106: Good: Human-only description is now explicit

Passing Descriptions.sampleDashboardHumans clarifies scope for the lab results component.


112-112: Good: Purpose tiles correctly labeled as human-only

Using Descriptions.sampleDashboardHumans aligns messaging across widgets.


190-194: Good: Null-safe merge for environment test result counts

Thanks for guarding getEnvironmentTestResultCountsByResultType() with Optional and merging via Long::sum.

sormas-ui/src/test/java/de/symeda/sormas/ui/dashboard/sample/SampleDashboardDataProviderTest.java (3)

60-63: Verify intent: Disease set while withNoDisease=true.

You’re setting both disease (non-null) and withNoDisease=true. If the intended semantics are mutual exclusivity, this combination might be misleading in a unit test that primarily validates criteria propagation.

If the UI/provider enforces exclusivity in production, consider either:

  • Clearing withNoDisease when disease is set, or
  • Adding a separate test that validates the exclusivity behavior explicitly.

108-125: Good negative coverage: disease filter excludes environment and human samples.

Solid checks that combining an active disease filter with environment samples yields zero environment counts and also excludes unrelated human samples (AFP vs created CORONAVIRUS). This guards against mixing domains.


126-130: LGTM; duplicate assertion from earlier review is resolved.

The assertions are now unique (environment test result counts, env specimen condition counts, and total environment sample count), addressing the prior duplication note.

sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardService.java (6)

48-48: LGTM: Required import for environment material

The new EnvironmentSampleMaterial import is used below and is appropriate.


151-157: Confirm semantics: received-only filter for environment specimen-condition counts

Human samples restrict to received=true for specimen-condition counts. The environment counterpart currently includes all environment samples regardless of received flag. If parity is intended, add a received filter; otherwise, please confirm the divergence is desired.

Example change:

-		return getEnvironmentSampleCountsBySpecimenCondition(
-				EnvironmentSample.SPECIMEN_CONDITION,
-				SpecimenCondition.class,
-				dashboardCriteria,
-				null);
+		return getEnvironmentSampleCountsBySpecimenCondition(
+				EnvironmentSample.SPECIMEN_CONDITION,
+				SpecimenCondition.class,
+				dashboardCriteria,
+				(cb, root) -> cb.equal(root.get(EnvironmentSample.RECEIVED), true));

221-223: Good fix: merge duplicate shipment-status keys after normalization

Using Long::sum prevents duplicate-key exceptions when different (shipped, received) pairs normalize to the same SampleShipmentStatus (e.g., null and false both map to false).


225-242: Verify DISPATCHED/RECEIVED semantics and normalization

Implementation mirrors human shipment counts and aggregates pairs via merge. Please confirm:

  • DISPATCHED is the intended “shipped” analog for environment samples.
  • Null flags should normalize to false (Boolean.TRUE.equals(...)) and be merged accordingly.

287-291: Mirror the null-result filter in the human counterpart for consistency

The human variant getTestResultCountsByResultType (Lines 262-265) still collects without filtering null keys; it may NPE for tests without a result. Please align it with this implementation.

Suggested stream ending for the human method:

return QueryHelper.getResultList(em, cq, null, null, Function.identity())
    .stream()
    .filter(t -> t.get(0) != null)
    .collect(Collectors.toMap(t -> (PathogenTestResultType) t.get(0), t -> (Long) t.get(1)));

573-575: LGTM: environment material filter correctly uses EnvironmentSampleMaterial

Switching to criteria.getEnvironmentSampleMaterial and EnvironmentSample.SAMPLE_MATERIAL is consistent with the new environment-specific criteria.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardService.java (1)

268-292: Nice: null-key filter avoids toMap NPE; align the human counterpart for consistency

The null-key filter here is correct and prevents NPEs when PathogenTest.TEST_RESULT is null (LEFT JOIN). Please apply the same safeguard to the human method getTestResultCountsByResultType for consistency and robustness.

  • Option A (stream-level): add .filter(t -> t.get(0) != null) before collecting.
  • Option B (DB-level): add cb.isNotNull(pathogenTestResult) to the WHERE clause and omit the stream filter.

Example for the human method (illustrative only; outside this range):

return QueryHelper.getResultList(em, cq, null, null, Function.identity())
    .stream()
    .filter(t -> t.get(0) != null)
    .collect(Collectors.toMap(t -> (PathogenTestResultType) t.get(0), t -> (Long) t.get(1)));

Option B is preferable to avoid transferring null-key rows from the DB.

Run this script to list all toMap usages in this file that may still collect null keys without filtering:

#!/bin/bash
set -euo pipefail
file="sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardService.java"

echo "Potential risky toMap usages (no preceding null-key filter on t.get(0)):"
# Find lines with toMap and include a bit of context
rg -nA2 -e 'Collectors\.toMap\(' "$file"

echo
echo "Occurrences of stream null-key filters:"
rg -n -e '\.filter\s*\(\s*t\s*->\s*t\.get\(\s*0\s*\)\s*!=\s*null\s*\)' "$file" || true
🧹 Nitpick comments (2)
sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardService.java (2)

159-179: Filter null grouping keys at the DB level to avoid unnecessary rows

Good call adding a stream-level null-key filter to avoid Collectors.toMap NPEs. You can push this check into the WHERE clause to avoid transferring null-key groups from the DB and drop the stream filter.

Suggested change:

-        cq.where(CriteriaBuilderHelper.and(cb, criteriaFilter, additionalFilters != null ? additionalFilters.apply(cb, sample) : null));
+        cq.where(CriteriaBuilderHelper.and(
+            cb,
+            criteriaFilter,
+            additionalFilters != null ? additionalFilters.apply(cb, sample) : null,
+            cb.isNotNull(groupingProperty)));

With that in place, the stream .filter(t -> t.get(0) != null) becomes redundant and can be removed.


300-314: Push null-material filtering into WHERE and simplify the stream

Current stream-level filter is correct; you can offload it to the database and remove the stream filter.

Apply this diff:

-        cq.where(criteriaFilter);
+        cq.where(CriteriaBuilderHelper.and(cb, criteriaFilter, cb.isNotNull(groupingProperty)));
@@
-        return QueryHelper.getResultList(em, cq, null, null, Function.identity())
-                .stream()
-                .filter(t -> t.get(0) != null)
-                .collect(Collectors.toMap(t -> (EnvironmentSampleMaterial) t.get(0), t -> (Long) t.get(1)));
+        return QueryHelper.getResultList(em, cq, null, null, Function.identity())
+                .stream()
+                .collect(Collectors.toMap(t -> (EnvironmentSampleMaterial) t.get(0), t -> (Long) t.get(1)));
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 74f3cda and 2cf766c.

📒 Files selected for processing (2)
  • sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardFacadeEjb.java (3 hunks)
  • sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardService.java (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardFacadeEjb.java
🧰 Additional context used
🧬 Code Graph Analysis (1)
sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardService.java (2)
sormas-backend/src/main/java/de/symeda/sormas/backend/common/CriteriaBuilderHelper.java (1)
  • CriteriaBuilderHelper (30-230)
sormas-backend/src/main/java/de/symeda/sormas/backend/util/QueryHelper.java (1)
  • QueryHelper (22-353)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: SORMAS CI
  • GitHub Check: android app test (26)
  • GitHub Check: android app test (28)
  • GitHub Check: android app test (27)
  • GitHub Check: Lint Code Base
🔇 Additional comments (5)
sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardService.java (5)

48-48: Import usage looks correct

EnvironmentSampleMaterial is used in the new environment counts method. No issues.


151-157: Parity with human counts: should we restrict to received environment samples?

Human specimen-condition counts filter to external + received = true. The environment counterpart currently passes no additional filter, so it counts all env samples regardless of received status. If parity is desired, add a received=true filter.

Apply this diff:

-        return getEnvironmentalSampleCountsBySpecimenCondition(
-                EnvironmentSample.SPECIMEN_CONDITION,
-                SpecimenCondition.class,
-                dashboardCriteria,
-                null);
+        return getEnvironmentalSampleCountsBySpecimenCondition(
+                EnvironmentSample.SPECIMEN_CONDITION,
+                SpecimenCondition.class,
+                dashboardCriteria,
+                (cb, root) -> cb.equal(root.get(EnvironmentSample.RECEIVED), true));

221-224: Good fix: merge function prevents duplicate-key errors; confirm null boolean mapping

Using a merge function for Collectors.toMap is the right choice because multiple shipped/received combinations can map to the same SampleShipmentStatus key. Please confirm that treating null booleans as false in getSampleShipmentStatusByFlags (via Boolean.TRUE.equals) is intended. Otherwise consider explicitly filtering or normalizing nulls in the query (e.g., cb.isNotNull on shipped/received) or mapping nulls to a distinct status.

Also applies to: 240-243


388-388: Method addition for environment sample map count looks good

Signature and usage mirror the human counterpart. No issues spotted.


575-577: Correct filter: use environment sample material criteria

Switching to criteria.getEnvironmentSampleMaterial for environment samples is correct and consistent with the new API.

@KarnaiahPesula KarnaiahPesula merged commit d918ad3 into development Aug 13, 2025
5 of 12 checks passed
@KarnaiahPesula KarnaiahPesula deleted the bugfix-13548-dashboard-samples-error branch August 13, 2025 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dashboard - Samples - Error when applying filter with sample selected

3 participants