Skip to content

RE1-T115 Bug fixes#372

Merged
ucswift merged 2 commits intomasterfrom
develop
May 7, 2026
Merged

RE1-T115 Bug fixes#372
ucswift merged 2 commits intomasterfrom
develop

Conversation

@ucswift
Copy link
Copy Markdown
Member

@ucswift ucswift commented May 7, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced input validation to prevent invalid operations in map imports, user management, and weather alerts.
    • Improved weather alert provider reliability with more robust HTTP response handling and error diagnostics.
  • Improvements

    • Custom map import process now requires layer selection before proceeding.
    • Enhanced UI messaging when no layers are available for import; submit button properly disabled.

@request-info
Copy link
Copy Markdown

request-info Bot commented May 7, 2026

Thanks for opening this, but we'd appreciate a little more information. Could you update it with more details?

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Warning

Rate limit exceeded

@ucswift has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 38 minutes and 50 seconds before requesting another review.

To continue reviewing without waiting, purchase usage credits in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1304f6be-7490-4593-819f-835d33395629

📥 Commits

Reviewing files that changed from the base of the PR and between 9b1689e and 10ac020.

⛔ Files ignored due to path filters (2)
  • Core/Resgrid.Localization/Areas/User/CustomMaps/CustomMaps.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/CustomMaps/CustomMaps.en.resx is excluded by !**/*.resx
📒 Files selected for processing (4)
  • Core/Resgrid.Model/PlanAddon.cs
  • Providers/Resgrid.Providers.Weather/NwsWeatherAlertProvider.cs
  • Web/Resgrid.Web/Areas/User/Controllers/CustomMapsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/TemplatesController.cs
📝 Walkthrough

Walkthrough

The PR adds protobuf serialization support for plan-addon models and introduces defensive validation and error handling across services, controllers, and views to handle null/empty input parameters and improve HTTP error diagnostics.

Changes

Protobuf Serialization Setup

Layer / File(s) Summary
Data Model
Core/Resgrid.Model/PlanAddon.cs
PlanAddon class annotated with [ProtoContract] and [ProtoMember] attributes on PlanAddonId, PlanId, Plan, AddonType, Cost, and ExternalId. New TestExternalId property added; GetExternalKey() now returns TestExternalId in test mode, otherwise ExternalId.
Serializer Initialization
Core/Resgrid.Model/Helpers/SerializerHelper.cs
WarmUpProtobufSerializer() extended with Serializer.PrepareSerializer<PaymentAddon>() and Serializer.PrepareSerializer<PlanAddon>() to initialize protobuf serializers.

Defensive Validation & Error Handling

Layer / File(s) Summary
Service Layer Validation
Core/Resgrid.Services/CustomMapService.cs, Core/Resgrid.Services/UsersService.cs
ImportGeoJsonAsync and ImportKmlAsync now throw ArgumentException if layerId is null/whitespace. UsersService.GetUserById() returns null immediately when userId is null/whitespace instead of querying.
HTTP Error Handling
Providers/Resgrid.Providers.Weather/NwsWeatherAlertProvider.cs
FetchAlertsAsync replaces EnsureSuccessStatusCode() with explicit status handling: 304 returns empty, 5xx throws HttpRequestException with diagnostic context and truncated body, 4xx logs and returns empty. Response body read upfront for diagnostics; JSON validation and parsing remain unchanged.
Controller Validation
Web/Resgrid.Web/Areas/User/Controllers/CustomMapsController.cs, Web/Resgrid.Web/Areas/User/Controllers/PersonnelController.cs
CustomMapsController.ImportUpload validates layerId is provided before processing; returns import view with message if missing. PersonnelController.DeletePerson (GET and POST) adds null/whitespace guards for userId and model.UserId, redirecting to personnel index when invalid.
Controller Refactoring
Web/Resgrid.Web/Areas/User/Controllers/TemplatesController.cs
New private helper PopulateDropdowns() centralizes dropdown data loading (call priorities, call types) for new and edit template flows. Both New and Edit POST handlers call helper on validation failure to repopulate dropdowns before returning view.
View Layer
Web/Resgrid.Web/Areas/User/Views/CustomMaps/Import.cshtml
"TargetLayer" section now renders "NoLayersAvailable" message when Model.Layers is null or empty; submit button conditionally disabled when no layers available.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'RE1-T115 Bug fixes' is vague and generic, using non-descriptive terms that don't convey meaningful information about the specific changes in the changeset. Provide a more specific title that describes the main change, such as 'Add null/whitespace validation and improve error handling across services' or similar.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Web/Resgrid.Web/Areas/User/Controllers/CustomMapsController.cs (1)

274-310: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate that the selected layer belongs to the requested map before importing.

Line 280 only checks presence. A crafted request can still submit a layerId from another map, and Line 300/305/309 will import into that foreign layer.

Suggested fix
 public async Task<IActionResult> ImportUpload(string mapId, string layerId, IFormFile importFile, CancellationToken cancellationToken)
 {
   var map = await _customMapService.GetCustomMapByIdAsync(mapId);
   if (map == null || map.DepartmentId != DepartmentId)
     return RedirectToAction("Index");

   if (string.IsNullOrWhiteSpace(layerId))
   {
     var model = new CustomMapImportView();
     model.Map = map;
     model.Layers = await _customMapService.GetLayersForMapAsync(mapId);
     model.Imports = await _customMapService.GetImportsForMapAsync(mapId);
     model.Message = "Please select a target layer before importing. Create a layer first if none exist.";
     return View("Import", model);
   }
+
+  var selectedLayer = await _customMapService.GetLayerByIdAsync(layerId);
+  if (selectedLayer == null || !string.Equals(selectedLayer.IndoorMapId, mapId, StringComparison.Ordinal))
+  {
+    var model = new CustomMapImportView();
+    model.Map = map;
+    model.Layers = await _customMapService.GetLayersForMapAsync(mapId);
+    model.Imports = await _customMapService.GetImportsForMapAsync(mapId);
+    model.Message = "Please select a valid target layer for this map.";
+    return View("Import", model);
+  }

   if (importFile == null || importFile.Length == 0)
     return RedirectToAction("Import", new { id = mapId });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Web/Resgrid.Web/Areas/User/Controllers/CustomMapsController.cs` around lines
274 - 310, In ImportUpload, validate that the provided layerId actually belongs
to the map before performing any imports: after loading map (variable map) and
before processing importFile, fetch/verify the layer via the service (e.g.,
_customMapService.GetLayerByIdAsync(layerId) or by checking layerId exists in
await _customMapService.GetLayersForMapAsync(mapId)), and if the layer is null
or its MapId/CustomMapId doesn't match mapId (or DepartmentId), return
RedirectToAction("Import", new { id = mapId }) or show the same “select a target
layer” view; ensure this check occurs prior to any calls to
_customMapService.ImportGeoJsonAsync, ImportKmlAsync, etc., to prevent importing
into an unrelated layer.
🧹 Nitpick comments (2)
Web/Resgrid.Web/Areas/User/Controllers/TemplatesController.cs (1)

73-82: ⚡ Quick win

Reuse PopulateDropdowns in GET actions to remove remaining duplication.

Now that this helper exists, call it from New() and Edit(int id) too, so all dropdown construction stays in one place.

♻️ Suggested refactor
 public async Task<IActionResult> New()
 {
 	var model = new NewTemplateModel();
 	model.Template = new CallQuickTemplate();
-
-	var priorites = await _callsService.GetActiveCallPrioritiesForDepartmentAsync(DepartmentId);
-	model.CallPriorities = new SelectList(priorites, "DepartmentCallPriorityId", "Name", priorites.FirstOrDefault(x => x.IsDefault));
-
-
-	List<CallType> types = new List<CallType>();
-	types.Add(new CallType { CallTypeId = 0, Type = "No Type" });
-	types.AddRange(await _callsService.GetCallTypesForDepartmentAsync(DepartmentId));
-	model.CallTypes = new SelectList(types, "Type", "Type");
+	await PopulateDropdowns(model);

 	return View(model);
 }
 public async Task<IActionResult> Edit(int id)
 {
 	var model = new NewTemplateModel();
 	model.Template = new CallQuickTemplate();

 	model.Template = await _templatesService.GetCallQuickTemplateByIdAsync(id);

 	if (model.Template == null || model.Template.DepartmentId != DepartmentId)
 		return Unauthorized();	
-
-	var priorites = await _callsService.GetActiveCallPrioritiesForDepartmentAsync(DepartmentId);
-	model.CallPriorities = new SelectList(priorites, "DepartmentCallPriorityId", "Name", priorites.FirstOrDefault(x => x.IsDefault));
-
-
-	List<CallType> types = new List<CallType>();
-	types.Add(new CallType { CallTypeId = 0, Type = "No Type" });
-	types.AddRange(await _callsService.GetCallTypesForDepartmentAsync(DepartmentId));
-	model.CallTypes = new SelectList(types, "Type", "Type");
+	await PopulateDropdowns(model);

 	return View(model);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Web/Resgrid.Web/Areas/User/Controllers/TemplatesController.cs` around lines
73 - 82, The NewTemplateModel dropdown population is duplicated in the GET
actions; reuse the existing PopulateDropdowns(NewTemplateModel model) helper by
calling it from the New() GET and Edit(int id) GET actions after
creating/loading the model and before returning the view. Update
TemplatesController methods New() and Edit(int id) to await
PopulateDropdowns(model) so CallPriorities and CallTypes are built in one place
(refer to PopulateDropdowns and the New and Edit(int id) action methods).
Web/Resgrid.Web/Areas/User/Controllers/PersonnelController.cs (1)

672-673: ⚡ Quick win

Consider returning BadRequest() instead of a silent redirect for malformed POST input.

The null-conditional model?.UserId correctly collapses both a null model and a null/whitespace UserId into one check. However, silently redirecting to Index on a POST with malformed data hides the error from the caller — especially relevant for any non-browser client that expects HTTP error semantics. A 400 Bad Request would be more appropriate here.

♻️ Suggested change
-            if (string.IsNullOrWhiteSpace(model?.UserId))
-                return RedirectToAction("Index", "Personnel", new { area = "User" });
+            if (model == null || string.IsNullOrWhiteSpace(model.UserId))
+                return BadRequest();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Web/Resgrid.Web/Areas/User/Controllers/PersonnelController.cs` around lines
672 - 673, The POST handler currently checks if
(string.IsNullOrWhiteSpace(model?.UserId)) and performs a
RedirectToAction("Index", "Personnel", new { area = "User" }); — change this to
return BadRequest() so malformed POST input returns HTTP 400 instead of silently
redirecting; update the action method in PersonnelController where the
model?.UserId check exists to return BadRequest() (or BadRequest(ModelState) if
you prefer) and keep the null/whitespace check logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Core/Resgrid.Model/PlanAddon.cs`:
- Around line 31-32: Update the PlanAddon.GetExternalKey() logic to treat the
new TestExternalId as a fallback when the normal external key is null/empty in
test scenarios: inside the GetExternalKey() method (class PlanAddon) check if
the primary external key is null/empty and if so return TestExternalId (if
non-empty) before returning null; ensure this only acts as a fallback
(preserving the original external id precedence) so production behavior remains
unchanged.

In `@Providers/Resgrid.Providers.Weather/NwsWeatherAlertProvider.cs`:
- Line 101: Replace the System.Diagnostics.Debug.WriteLine(errorMsg) call in
NwsWeatherAlertProvider with the Resgrid logging API: call
Resgrid.Framework.Logging.LogError(...) (or LogException(...) if you have an
Exception instance) passing the same error message and any available
exception/context so the error goes through centralized logging; look for the
Debug.WriteLine(errorMsg) usage in the class/method and swap it to
Resgrid.Framework.Logging.LogError(errorMsg) (or LogException(exception,
errorMsg)) accordingly.
- Line 83: The call to response.Content.ReadAsStringAsync() can NRE if
response.Content is null; in the NwsWeatherAlertProvider (where response and
json are used) first check response.Content != null before calling
ReadAsStringAsync(), e.g. set json to string.Empty or null when Content is null
and then proceed with the existing status-specific handling; update any
downstream logic that assumes json non-null (variables: response,
response.Content, json) so null/empty content is handled gracefully.

In `@Web/Resgrid.Web/Areas/User/Controllers/CustomMapsController.cs`:
- Line 286: The hard-coded message assigned to model.Message in
CustomMapsController should be replaced with a localized resource lookup: add a
new resource key (e.g., "PleaseSelectTargetLayer") to the project's resource
file(s) and use the controller's existing localization mechanism (the same
pattern used elsewhere in this controller/page, e.g.,
_localizer["PleaseSelectTargetLayer"] or Resources.PleaseSelectTargetLayer) when
assigning model.Message so the string is retrieved from resources instead of
being hard-coded.

In `@Web/Resgrid.Web/Areas/User/Controllers/TemplatesController.cs`:
- Around line 75-77: The SelectList is being constructed with the selectedValue
set to an object (priorites.FirstOrDefault(x => x.IsDefault)) instead of the
scalar key; change the selectedValue to the default item's
DepartmentCallPriorityId. Locate the call that assigns model.CallPriorities
(using _callsService.GetActiveCallPrioritiesForDepartmentAsync and the priorites
variable) and pass priorites.FirstOrDefault(x =>
x.IsDefault)?.DepartmentCallPriorityId as the fourth parameter to new
SelectList(...) so the selected value matches the "DepartmentCallPriorityId"
value field.

---

Outside diff comments:
In `@Web/Resgrid.Web/Areas/User/Controllers/CustomMapsController.cs`:
- Around line 274-310: In ImportUpload, validate that the provided layerId
actually belongs to the map before performing any imports: after loading map
(variable map) and before processing importFile, fetch/verify the layer via the
service (e.g., _customMapService.GetLayerByIdAsync(layerId) or by checking
layerId exists in await _customMapService.GetLayersForMapAsync(mapId)), and if
the layer is null or its MapId/CustomMapId doesn't match mapId (or
DepartmentId), return RedirectToAction("Import", new { id = mapId }) or show the
same “select a target layer” view; ensure this check occurs prior to any calls
to _customMapService.ImportGeoJsonAsync, ImportKmlAsync, etc., to prevent
importing into an unrelated layer.

---

Nitpick comments:
In `@Web/Resgrid.Web/Areas/User/Controllers/PersonnelController.cs`:
- Around line 672-673: The POST handler currently checks if
(string.IsNullOrWhiteSpace(model?.UserId)) and performs a
RedirectToAction("Index", "Personnel", new { area = "User" }); — change this to
return BadRequest() so malformed POST input returns HTTP 400 instead of silently
redirecting; update the action method in PersonnelController where the
model?.UserId check exists to return BadRequest() (or BadRequest(ModelState) if
you prefer) and keep the null/whitespace check logic unchanged.

In `@Web/Resgrid.Web/Areas/User/Controllers/TemplatesController.cs`:
- Around line 73-82: The NewTemplateModel dropdown population is duplicated in
the GET actions; reuse the existing PopulateDropdowns(NewTemplateModel model)
helper by calling it from the New() GET and Edit(int id) GET actions after
creating/loading the model and before returning the view. Update
TemplatesController methods New() and Edit(int id) to await
PopulateDropdowns(model) so CallPriorities and CallTypes are built in one place
(refer to PopulateDropdowns and the New and Edit(int id) action methods).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4772444c-a7db-4e4a-a6ba-02335b009492

📥 Commits

Reviewing files that changed from the base of the PR and between a79a7a4 and 9b1689e.

⛔ Files ignored due to path filters (2)
  • Core/Resgrid.Localization/Areas/User/CustomMaps/CustomMaps.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/CustomMaps/CustomMaps.en.resx is excluded by !**/*.resx
📒 Files selected for processing (9)
  • Core/Resgrid.Model/Helpers/SerializerHelper.cs
  • Core/Resgrid.Model/PlanAddon.cs
  • Core/Resgrid.Services/CustomMapService.cs
  • Core/Resgrid.Services/UsersService.cs
  • Providers/Resgrid.Providers.Weather/NwsWeatherAlertProvider.cs
  • Web/Resgrid.Web/Areas/User/Controllers/CustomMapsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/PersonnelController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/TemplatesController.cs
  • Web/Resgrid.Web/Areas/User/Views/CustomMaps/Import.cshtml

Comment thread Core/Resgrid.Model/PlanAddon.cs
Comment thread Providers/Resgrid.Providers.Weather/NwsWeatherAlertProvider.cs Outdated
Comment thread Providers/Resgrid.Providers.Weather/NwsWeatherAlertProvider.cs Outdated
Comment thread Web/Resgrid.Web/Areas/User/Controllers/CustomMapsController.cs Outdated
Comment thread Web/Resgrid.Web/Areas/User/Controllers/TemplatesController.cs
@ucswift
Copy link
Copy Markdown
Member Author

ucswift commented May 7, 2026

Approve

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

This PR is approved.

@ucswift ucswift merged commit 034e26f into master May 7, 2026
18 of 19 checks passed
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.

1 participant