Skip to content

RG-T125 IC bug fixes, weather alert fixes#422

Merged
ucswift merged 3 commits into
masterfrom
develop
Jul 14, 2026
Merged

RG-T125 IC bug fixes, weather alert fixes#422
ucswift merged 3 commits into
masterfrom
develop

Conversation

@ucswift

@ucswift ucswift commented Jul 14, 2026

Copy link
Copy Markdown
Member

Pull Request Description

RG-T125: IC Bug Fixes and Weather Alert Fixes

This pull request delivers a comprehensive overhaul of the Command Board template system for Incident Command, fixes weather alert notifications, and hardens several OIDC database migration paths.


1. Command Board Templates — Full Lifecycle & Persistence

Previously, command board definitions stored their lanes (Divisions, Groups, Staging, etc.) but the lanes' required unit types and required personnel roles were never reliably persisted, hydrated, or reconciled. This PR implements:

  • Full-replace reconcile on Save: lanes are inserted/updated/deleted atomically in a single transaction; their requirement rows (unit types, personnel roles) are fully replaced. A null lane collection leaves existing lanes untouched; an empty collection clears them.
  • Hydration on read: GetAllCommandsForDepartmentAsync, GetCommandByIdAsync, and GetCommandForCallTypeAsync now return lanes with their requirement sets populated and ordered by SortOrder.
  • Cascade delete: deleting a command definition removes its lanes and their requirement rows first, in one transaction.
  • Security hardening: a foreign role id (from another definition) is treated as an insert rather than hijacking an existing row; call-type binding is validated against the department's own call types.

2. Lane Requirement Enforcement (Forced vs. Advisory)

When a resource is assigned to or moved into a lane whose template role defines requirements:

  • Forced lanes (ForceRequirements = true): the assignment/move is rejected with CommandRequirementsNotMetException if an own-department unit doesn't match a required unit type, or personnel don't hold a required role. The v4 API surfaces the reason as a failure message.
  • Advisory lanes (ForceRequirements = false): the assignment succeeds, but the violation is stamped on the assignment (RequirementsWarning, RequirementsWarningMessage) so the IC app can render a warning indicator on the resource chip — including via offline delta sync.
  • Exemptions: mutual-aid/linked-department and ad-hoc resources carry no own-department type/role metadata, so they are never gated. Requirements are only evaluated against lanes on the same incident (same department + call).
  • Added a database migration (M0088) for the two new ResourceAssignments columns.

3. Auto-Apply Default Board on Command Establishment

When command is established on a call, the system now auto-resolves the department's template for that call's type (falling back to the "Any Call Type" board), seeds the board lanes in template order, and starts the template's repeating benchmark timer (if configured). Previously, lane seeding only happened when an explicit commandDefinitionId was passed and lane data happened to be loaded.

4. Removal of Certification Requirements (Cleanup)

The CommandDefinitionRoleCert / RequiredCerts concept — never functional — is removed. The model property is deleted, the orphaned CommandDefinitionRoleCerts table is dropped via migration (M0087), and the UI no longer collects certifications.

5. Weather Alert Notification Fixes

  • Added MessageTypes.WeatherAlert = 3.
  • Weather alert messages now carry their own descriptive title (e.g., the alert subject) instead of the generic "Msg:" prefix, and a custom push subtitle (the alert headline) instead of "Msg from System".
  • Added a non-persisted Message.PushSubTitle so the weather service can supply the headline.

6. OIDC SQL Migration Hardening

The EF OIDC bootstrap and V5-migration SQL scripts now correctly detect databases that are already V5-shaped (created via EF EnsureCreated) and stamp the migration history row, rather than attempting a Type → ClientType rename that fails when the Type column doesn't exist. Each guard now requires all expected columns/tables to be present before stamping, preventing false-positive skips on partial schemas.

7. Command Boards Web UI (Admin)

The legacy, half-functional New/Edit/View pages for command definitions are replaced with a working Command Boards management UI:

  • Index: lists boards with their call type (resolved by name) and lane count; Delete is now a proper POST form with anti-forgery.
  • New / Edit / View: shared _CommandForm partial; the assignment modal uses real multi-select pickers populated from the department's unit types and personnel roles (replacing the previous AJAX text inputs that pointed at non-existent endpoints). Lane type, description, requirements, and "Force Requirements" are all captured. The View page renders a read-only summary of lanes and their requirements.
  • Server-side form parsing (ParseAssignmentsFromForm) assigns SortOrder from row order and normalizes the posted CSV id lists.

8. API v4 Updates

  • CommandsController.SaveCommand now persists RequiredUnitTypes and RequiredPersonnelRoles per lane, and ConvertCommandData returns them.
  • IncidentCommandController assign/move endpoints catch CommandRequirementsNotMetException (returning a failure with the reason) and surface advisory warnings via the response Message.

9. CI / Release Notes

The release-notes extraction step now also normalizes Pull Request Description headings to Summary.

10. Tests

Added comprehensive unit tests covering: lane hydration, call-type resolution with "Any Call Type" fallback, Save reconcile (insert/update/delete, foreign-id protection, null handling, requirement replacement), cascade delete, GetRoleWithRequirementsAsync, and the full lane-requirement enforcement matrix (forced reject, advisory warning stamp/clear, mutual-aid exemption, foreign-call-lane exemption, move rejection).


Migrations: M0087 (drop CommandDefinitionRoleCerts) and M0088 (add RequirementsWarning / RequirementsWarningMessage to ResourceAssignments), shipped for both SQL Server and PostgreSQL.

@request-info

request-info Bot commented Jul 14, 2026

Copy link
Copy Markdown

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

@Resgrid-Bot

Resgrid-Bot commented Jul 14, 2026

Copy link
Copy Markdown

Code Review Could Not Complete ⚠️

The review failed before suggestions could be generated.

Reason: Transient error reaching the provider. Try again.

After fixing the issue, comment @kody review on this PR to re-run the review.

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug
Performance
Security
Business Logic

Access your configuration settings here.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9bf2ce91-61ad-4a59-b346-391990e03fd0

📥 Commits

Reviewing files that changed from the base of the PR and between 1268f1e and 9be682a.

📒 Files selected for processing (1)
  • Web/Resgrid.Web/wwwroot/js/app/internal/command/resgrid.commands.newcommand.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • Web/Resgrid.Web/wwwroot/js/app/internal/command/resgrid.commands.newcommand.js

📝 Walkthrough

Walkthrough

This PR adds command-lane requirement persistence, hydration, reconciliation, enforcement, warnings, API exposure, and web editing support. It also adds weather-alert push metadata, removes command-role certification mappings, updates migration safeguards, and normalizes release-note headings.

Changes

Command board lane requirements

Layer / File(s) Summary
Requirement contracts and persistence
Core/Resgrid.Model/..., Repositories/Resgrid.Repositories.DataRepository/..., Providers/Resgrid.Providers.Migrations...
Adds lane requirement contracts, SQL queries, repositories, DI registrations, warning columns, and certification-mapping removal.
Command hydration and reconciliation
Core/Resgrid.Services/CommandsService.cs, Core/Resgrid.Services/IncidentCommandService.cs
Hydrates and reconciles lane requirements, resolves command templates, and enforces or reports unmet destination requirements.
Command API and web board UI
Web/Resgrid.Web.Services/..., Web/Resgrid.Web/Areas/User/..., Web/Resgrid.Web/wwwroot/...
Exposes requirement and warning fields and adds command-board creation, editing, viewing, modal, partial, and JavaScript support.

Weather alert notifications

Layer / File(s) Summary
Weather alert message metadata and push formatting
Core/Resgrid.Model/Message.cs, Core/Resgrid.Model/MessageTypes.cs, Core/Resgrid.Services/WeatherAlertService.cs, Core/Resgrid.Services/CommunicationService.cs
Adds the WeatherAlert type and push subtitle, then applies weather-specific push title and subtitle formatting.

Migration and release safeguards

Layer / File(s) Summary
Release and migration guards
.github/workflows/dotnet.yml, Providers/Resgrid.Providers.Migrations/Sql/*
Renames pull-request description headings in release notes and guards existing OpenIddict schema transitions.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant APIClient
  participant IncidentCommandController
  participant IncidentCommandService
  participant CommandsService
  participant ResourceAssignment
  APIClient->>IncidentCommandController: assign or move resource
  IncidentCommandController->>IncidentCommandService: submit assignment change
  IncidentCommandService->>CommandsService: retrieve lane requirements
  IncidentCommandService->>IncidentCommandService: evaluate unit and personnel roles
  IncidentCommandService->>ResourceAssignment: persist warning or reject operation
  IncidentCommandService-->>IncidentCommandController: return assignment or failure
  IncidentCommandController-->>APIClient: return data and message
Loading

Possibly related PRs

  • Resgrid/Core#416: Introduces the incident-command domain and models used by lane requirement warnings.
  • Resgrid/Core#421: Extends qualification behavior used by lane requirement evaluation.
  • Resgrid/Core#409: Modifies the same weather-alert notification method.

Suggested reviewers: github-actions

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.61% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main themes of the changeset: Incident Command fixes and weather alert fixes.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🧹 Nitpick comments (6)
Core/Resgrid.Services/CommunicationService.cs (1)

96-115: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use string interpolation for improved readability.

As per coding guidelines, prefer modern C# features appropriately. Using string interpolation instead of string.Format and string concatenation makes the code more concise and readable.

🛠️ Proposed refactor
 				else
 				{
 					if (message.SystemGenerated)
 						spm.SubTitle = "Msg from System";
 					else
-						spm.SubTitle = string.Format("Msg from {0}", sendersName);
+						spm.SubTitle = $"Msg from {sendersName}";
 
-					spm.Title = "Msg:" + message.Subject.Truncate(200);
+					spm.Title = $"Msg:{message.Subject.Truncate(200)}";
 				}
🤖 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 `@Core/Resgrid.Services/CommunicationService.cs` around lines 96 - 115, Update
the non-weather-alert title and sender subtitle assignments in the communication
message formatting block to use C# string interpolation instead of string.Format
and string concatenation, while preserving the existing output text and
truncation behavior.

Source: Coding guidelines

Core/Resgrid.Model/IncidentCommand/CommandRequirementsNotMetException.cs (1)

10-15: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add standard exception constructors.

Only a (string message) constructor is provided. Per .NET conventions (CA1032), custom exceptions should also expose parameterless and (string, Exception) constructors for consistency with the base Exception API and to support re-throwing with an inner exception.

♻️ Suggested additional constructors
 public class CommandRequirementsNotMetException : Exception
 {
+    public CommandRequirementsNotMetException()
+    {
+    }
+
     public CommandRequirementsNotMetException(string message) : base(message)
     {
     }
+
+    public CommandRequirementsNotMetException(string message, Exception innerException) : base(message, innerException)
+    {
+    }
 }
🤖 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 `@Core/Resgrid.Model/IncidentCommand/CommandRequirementsNotMetException.cs`
around lines 10 - 15, Update CommandRequirementsNotMetException to expose
parameterless and (string message, Exception innerException) constructors in
addition to the existing message constructor, delegating each to the
corresponding Exception base constructor.
Core/Resgrid.Services/IncidentCommandService.cs (2)

721-784: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Requirement evaluation logic looks correct; note the per-call GetUnitTypesForDepartmentAsync fetch.

Unit-type validation re-fetches all department unit types on every RealUnit assign/move to map unit.Type (name) back to an id. This is a full department-scoped read on a per-assignment hot path (command board). Given board interactions can be frequent, consider whether this list should be cached (per repo caching guidelines) at the IUnitsService layer if not already.

🤖 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 `@Core/Resgrid.Services/IncidentCommandService.cs` around lines 721 - 784, The
per-assignment unit-type lookup in EvaluateNodeRequirementsAsync repeatedly
calls GetUnitTypesForDepartmentAsync, creating unnecessary department-scoped
reads on the command-board hot path. Reuse an existing IUnitsService caching
mechanism or add repository-standard caching at that service layer for
department unit types, while preserving the current name-to-UnitTypeId matching
behavior.

Source: Coding guidelines


37-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Two more constructor-injected dependencies added to an already long constructor.

Per coding guidelines, dependencies should be resolved via Bootstrapper.GetKernel().Resolve<T>() (Service Locator) rather than constructor injection, and constructor injection should be minimized. This class already uses constructor injection exclusively for ~15 existing dependencies; these two new ones follow that established (if guideline-inconsistent) file-wide convention. Not asking for a rewrite here, but flagging per guidelines for awareness — retrofitting this file's DI style is out of scope for this change.

Also applies to: 55-57, 74-75

🤖 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 `@Core/Resgrid.Services/IncidentCommandService.cs` around lines 37 - 38, No
change is required for this comment: retain _unitsService and
_personnelRolesService as constructor-injected dependencies, consistent with the
existing IncidentCommandService pattern. Do not retrofit the class to use
Bootstrapper.GetKernel().Resolve<T>() as part of this change.

Source: Coding guidelines

Core/Resgrid.Services/CommandsService.cs (2)

116-129: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

New read paths bypass ICacheProvider.

HydrateAssignmentsAsync and GetRoleWithRequirementsAsync (and the callers that route through them) read role/unit-type/personnel-role data directly from repositories on every call, with no ICacheProvider.RetrieveAsync<T>() cache-aside wrapper and no bypassCache parameter. As per coding guidelines, "All caching operations must go through ICacheProvider.Retrieve<T>() or ICacheProvider.RetrieveAsync<T>() using the cache-aside pattern with fallback functions" and "Service methods should include a bypassCache parameter (default: false)" for Core/Resgrid.Services/**/*.cs. If CommandsService never cached this data before this PR, this may be an acceptable pre-existing gap rather than a regression — flagging for awareness given these are new read methods on a likely hot path (e.g. resolving the command board for a call).

Also applies to: 143-166

🤖 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 `@Core/Resgrid.Services/CommandsService.cs` around lines 116 - 129, Update
GetRoleWithRequirementsAsync and HydrateAssignmentsAsync, plus their relevant
callers, to use ICacheProvider.RetrieveAsync<T>() with repository fallback
functions for role, unit-type, and personnel-role reads. Add an optional
bypassCache parameter defaulting to false and propagate it through the call
chain, preserving the current hydrated results and null/empty collection
behavior.

Source: Coding guidelines


29-44: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Per-command hydration loop N+1s across a department's command boards.

GetAllCommandsForDepartmentAsync calls HydrateAssignmentsAsync once per command definition, and each call issues 3 queries (roles, unit types, personnel roles) scoped to that single command. For a department with many command boards, this is O(3n) round-trips instead of a single batched fetch across the whole department (similar to how HydrateAssignmentsAsync already batches per-command instead of per-role). Consider adding department-scoped bulk queries for unit types/personnel roles, mirroring the by-command-id queries added in this PR.

🤖 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 `@Core/Resgrid.Services/CommandsService.cs` around lines 29 - 44, The
per-command HydrateAssignmentsAsync loop in GetAllCommandsForDepartmentAsync
causes three database round-trips per command. Add department-scoped bulk
queries for roles, unit types, and personnel roles, then hydrate all returned
command definitions from those shared results while preserving each command’s
assignments; follow the existing by-command-id query patterns and avoid invoking
HydrateAssignmentsAsync once per command.
🤖 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 @.github/workflows/dotnet.yml:
- Line 146: Update the replacement in the workflow’s PR-body transformation to
match only the Markdown section heading “Pull Request Description,” including
its heading syntax, rather than every case-insensitive occurrence. Preserve
prose and code-block content while still renaming the intended heading to
“Summary.”

In `@Core/Resgrid.Model/Services/ICommandsService.cs`:
- Around line 39-46: Update ICommandsService.GetRoleWithRequirementsAsync to
accept an optional bool bypassCache parameter defaulting to false, and propagate
this parameter through its implementation and callers, especially
IncidentCommandService.EvaluateNodeRequirementsAsync, so live requirement
evaluation can request fresh data when needed.

In `@Core/Resgrid.Services/CommandsService.cs`:
- Around line 46-88: Wrap the full reconciliation flows in Save and DeleteAsync,
including CommandDefinitionRole and requirement updates, in a shared IUnitOfWork
transaction with commit on success and rollback on failure. Ensure all
repository operations use that same unit-of-work boundary, and preserve the
existing null-versus-empty assignment behavior while preventing concurrent saves
from reconciling against an unprotected existing snapshot.

In `@Core/Resgrid.Services/IncidentCommandService.cs`:
- Around line 638-656: The node validation in MoveResourceAsync must also
require node.CallId to match the assignment’s CallId before evaluating
requirements or persisting CommandStructureNodeId. Update the existing node !=
null and DepartmentId condition to include the CallId match, preserving
rejection/prevention of foreign-incident lanes.

In `@Providers/Resgrid.Providers.Migrations/Sql/EF0001_PopulateOIDCDb.sql`:
- Around line 14-20: Update the guard around the EF0001 migration-history insert
in Providers/Resgrid.Providers.Migrations/Sql/EF0001_PopulateOIDCDb.sql (lines
14-20) to verify every table or object created by CreateOpenIddictModels, not
just AspNetRoles, before stamping the migration. Apply the same complete-schema
validation in
Providers/Resgrid.Providers.Migrations/Sql/EF0003_MigrateOIDCDbToV5.sql (lines
6-10): when Type is absent, require all V5 columns before inserting the
migration-history row.

In
`@Repositories/Resgrid.Repositories.DataRepository/CommandDefinitionRepository.cs`:
- Around line 41-48: The three repository constructors in
Repositories/Resgrid.Repositories.DataRepository/CommandDefinitionRepository.cs
at lines 41-48, 106-112, and 133-139—CommandDefinitionRoleRepository,
CommandDefinitionRoleUnitTypeRepository, and
CommandDefinitionRolePersonnelRoleRepository—must stop accepting injected
dependencies and resolve each dependency required by the base constructor
through Bootstrapper.GetKernel().Resolve<T>(). Preserve the existing
base-constructor dependency order and repository initialization behavior at all
three sites.
- Around line 69-71: Update the QueryAsync delegate to access the transaction
through the nullable unitOfWork parameter, matching the existing
unitOfWork?.Connection null check and preventing a NullReferenceException when
unitOfWork is absent.

In `@Web/Resgrid.Web.Services/Controllers/v4/IncidentCommandController.cs`:
- Around line 264-277: Add Resgrid.Framework.Logging.LogException(ex) at the
start of both CommandRequirementsNotMetException catch blocks in AssignResource
and MoveResource within IncidentCommandController.cs:264-277 and
IncidentCommandController.cs:304-317, before setting result.Status. No other
response behavior should change.

In `@Web/Resgrid.Web/Areas/User/Controllers/CommandController.cs`:
- Around line 156-166: Change the CommandController.Delete action from HttpGet
to HttpPost and add ValidateAntiForgeryToken, preserving its existing
authorization, ownership check, deletion, and redirect behavior. Update the
corresponding delete control in Index.cshtml to submit a POST form carrying the
anti-forgery token instead of navigating through an anchor link.
- Around line 60-87: The New and Edit POST actions in CommandController must
validate the posted SelectedType against the current department’s call-type
collection before assigning CallTypeId. Reuse the department call-type data
loaded by PopulateSupportingDataAsync or the established source, accept only a
valid department-associated value, and otherwise assign null or reject
validation; apply this change at both CommandController.cs ranges 60-87 (New)
and 107-137 (Edit).

---

Nitpick comments:
In `@Core/Resgrid.Model/IncidentCommand/CommandRequirementsNotMetException.cs`:
- Around line 10-15: Update CommandRequirementsNotMetException to expose
parameterless and (string message, Exception innerException) constructors in
addition to the existing message constructor, delegating each to the
corresponding Exception base constructor.

In `@Core/Resgrid.Services/CommandsService.cs`:
- Around line 116-129: Update GetRoleWithRequirementsAsync and
HydrateAssignmentsAsync, plus their relevant callers, to use
ICacheProvider.RetrieveAsync<T>() with repository fallback functions for role,
unit-type, and personnel-role reads. Add an optional bypassCache parameter
defaulting to false and propagate it through the call chain, preserving the
current hydrated results and null/empty collection behavior.
- Around line 29-44: The per-command HydrateAssignmentsAsync loop in
GetAllCommandsForDepartmentAsync causes three database round-trips per command.
Add department-scoped bulk queries for roles, unit types, and personnel roles,
then hydrate all returned command definitions from those shared results while
preserving each command’s assignments; follow the existing by-command-id query
patterns and avoid invoking HydrateAssignmentsAsync once per command.

In `@Core/Resgrid.Services/CommunicationService.cs`:
- Around line 96-115: Update the non-weather-alert title and sender subtitle
assignments in the communication message formatting block to use C# string
interpolation instead of string.Format and string concatenation, while
preserving the existing output text and truncation behavior.

In `@Core/Resgrid.Services/IncidentCommandService.cs`:
- Around line 721-784: The per-assignment unit-type lookup in
EvaluateNodeRequirementsAsync repeatedly calls GetUnitTypesForDepartmentAsync,
creating unnecessary department-scoped reads on the command-board hot path.
Reuse an existing IUnitsService caching mechanism or add repository-standard
caching at that service layer for department unit types, while preserving the
current name-to-UnitTypeId matching behavior.
- Around line 37-38: No change is required for this comment: retain
_unitsService and _personnelRolesService as constructor-injected dependencies,
consistent with the existing IncidentCommandService pattern. Do not retrofit the
class to use Bootstrapper.GetKernel().Resolve<T>() as part of this change.
🪄 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: 400aba94-086c-466a-b8da-4dd475f3209c

📥 Commits

Reviewing files that changed from the base of the PR and between 3ce2cc2 and 63830b4.

⛔ Files ignored due to path filters (2)
  • Tests/Resgrid.Tests/Services/CommandsServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/IncidentCommandServiceParTests.cs is excluded by !**/Tests/**
📒 Files selected for processing (46)
  • .github/workflows/dotnet.yml
  • Core/Resgrid.Model/CommandDefinitionRole.cs
  • Core/Resgrid.Model/CommandDefinitionRoleCert.cs
  • Core/Resgrid.Model/IncidentCommand/CommandRequirementsNotMetException.cs
  • Core/Resgrid.Model/IncidentCommand/CommandStructureNode.cs
  • Core/Resgrid.Model/Message.cs
  • Core/Resgrid.Model/MessageTypes.cs
  • Core/Resgrid.Model/Repositories/ICommandDefinitionRepository.cs
  • Core/Resgrid.Model/Services/ICommandsService.cs
  • Core/Resgrid.Services/CommandsService.cs
  • Core/Resgrid.Services/CommunicationService.cs
  • Core/Resgrid.Services/IncidentCommandService.cs
  • Core/Resgrid.Services/WeatherAlertService.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0087_RemoveCommandDefinitionRoleCerts.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0088_AddResourceAssignmentRequirementsWarning.cs
  • Providers/Resgrid.Providers.Migrations/Sql/EF0001_PopulateOIDCDb.sql
  • Providers/Resgrid.Providers.Migrations/Sql/EF0003_MigrateOIDCDbToV5.sql
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0087_RemoveCommandDefinitionRoleCertsPg.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0088_AddResourceAssignmentRequirementsWarningPg.cs
  • Repositories/Resgrid.Repositories.DataRepository/CommandDefinitionRepository.cs
  • Repositories/Resgrid.Repositories.DataRepository/Configs/SqlConfiguration.cs
  • Repositories/Resgrid.Repositories.DataRepository/Modules/ApiDataModule.cs
  • Repositories/Resgrid.Repositories.DataRepository/Modules/DataModule.cs
  • Repositories/Resgrid.Repositories.DataRepository/Modules/NonWebDataModule.cs
  • Repositories/Resgrid.Repositories.DataRepository/Modules/TestingDataModule.cs
  • Repositories/Resgrid.Repositories.DataRepository/Queries/Commands/SelectCmdRolePersonnelRolesByCommandIdQuery.cs
  • Repositories/Resgrid.Repositories.DataRepository/Queries/Commands/SelectCmdRolePersonnelRolesByRoleIdQuery.cs
  • Repositories/Resgrid.Repositories.DataRepository/Queries/Commands/SelectCmdRoleUnitTypesByCommandIdQuery.cs
  • Repositories/Resgrid.Repositories.DataRepository/Queries/Commands/SelectCmdRoleUnitTypesByRoleIdQuery.cs
  • Repositories/Resgrid.Repositories.DataRepository/Queries/Commands/SelectCommandDefinitionRolesByCommandIdQuery.cs
  • Repositories/Resgrid.Repositories.DataRepository/Servers/PostgreSql/PostgreSqlConfiguration.cs
  • Repositories/Resgrid.Repositories.DataRepository/Servers/SqlServer/SqlServerConfiguration.cs
  • Web/Resgrid.Web.Services/Controllers/v4/CommandsController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/IncidentCommandController.cs
  • Web/Resgrid.Web.Services/Models/v4/Commands/CommandModels.cs
  • Web/Resgrid.Web.Services/Models/v4/IncidentCommand/IncidentCommandModels.cs
  • Web/Resgrid.Web/Areas/User/Controllers/CommandController.cs
  • Web/Resgrid.Web/Areas/User/Models/Command/CommandIndexView.cs
  • Web/Resgrid.Web/Areas/User/Models/Command/NewCommandView.cs
  • Web/Resgrid.Web/Areas/User/Views/Command/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Command/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Command/New.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Command/View.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Command/_AssignmentModal.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Command/_CommandForm.cshtml
  • Web/Resgrid.Web/wwwroot/js/app/internal/command/resgrid.commands.newcommand.js
💤 Files with no reviewable changes (1)
  • Core/Resgrid.Model/CommandDefinitionRoleCert.cs

if (pr) {
const body = (pr.body || '')
.replace(/##\s*Summary by CodeRabbit[\s\S]*/i, '')
.replace(/Pull Request Description/gi, 'Summary')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Restrict the replacement to the section heading.

Line 146 rewrites every occurrence in the PR body, including prose and code blocks. Match only the intended Markdown heading so release-note content is preserved.

🤖 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 @.github/workflows/dotnet.yml at line 146, Update the replacement in the
workflow’s PR-body transformation to match only the Markdown section heading
“Pull Request Description,” including its heading syntax, rather than every
case-insensitive occurrence. Preserve prose and code-block content while still
renaming the intended heading to “Summary.”

Comment on lines +39 to +46
/// <summary>
/// Gets a single lane (role) with its requirement sets (RequiredUnitTypes/RequiredRoles) hydrated.
/// Used to enforce lane requirements when assigning resources on the live command board.
/// </summary>
/// <param name="commandDefinitionRoleId">The lane (command definition role) identifier.</param>
/// <returns>Task&lt;CommandDefinitionRole&gt;.</returns>
Task<CommandDefinitionRole> GetRoleWithRequirementsAsync(int commandDefinitionRoleId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add a bypassCache parameter to GetRoleWithRequirementsAsync.

Per coding guidelines, service methods should expose a bypassCache (default false) parameter so callers can force fresh data. This method drives live enforcement/warning decisions on every resource assign/move (IncidentCommandService.EvaluateNodeRequirementsAsync); without a bypass option, a lane-requirement edit could be enforced against stale cached data until the cache expires. As per coding guidelines: "Service methods should include a bypassCache parameter (default: false) to allow callers to skip cache retrieval when fresh data is required."

♻️ Suggested signature change
-		Task<CommandDefinitionRole> GetRoleWithRequirementsAsync(int commandDefinitionRoleId);
+		Task<CommandDefinitionRole> GetRoleWithRequirementsAsync(int commandDefinitionRoleId, bool bypassCache = false);
📝 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
/// <summary>
/// Gets a single lane (role) with its requirement sets (RequiredUnitTypes/RequiredRoles) hydrated.
/// Used to enforce lane requirements when assigning resources on the live command board.
/// </summary>
/// <param name="commandDefinitionRoleId">The lane (command definition role) identifier.</param>
/// <returns>Task&lt;CommandDefinitionRole&gt;.</returns>
Task<CommandDefinitionRole> GetRoleWithRequirementsAsync(int commandDefinitionRoleId);
/// <summary>
/// Gets a single lane (role) with its requirement sets (RequiredUnitTypes/RequiredRoles) hydrated.
/// Used to enforce lane requirements when assigning resources on the live command board.
/// </summary>
/// <param name="commandDefinitionRoleId">The lane (command definition role) identifier.</param>
/// <returns>Task&lt;CommandDefinitionRole&gt;.</returns>
Task<CommandDefinitionRole> GetRoleWithRequirementsAsync(int commandDefinitionRoleId, bool bypassCache = false);
🤖 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 `@Core/Resgrid.Model/Services/ICommandsService.cs` around lines 39 - 46, Update
ICommandsService.GetRoleWithRequirementsAsync to accept an optional bool
bypassCache parameter defaulting to false, and propagate this parameter through
its implementation and callers, especially
IncidentCommandService.EvaluateNodeRequirementsAsync, so live requirement
evaluation can request fresh data when needed.

Source: Coding guidelines

Comment thread Core/Resgrid.Services/CommandsService.cs
Comment thread Core/Resgrid.Services/IncidentCommandService.cs
Comment on lines +41 to +48
public CommandDefinitionRoleRepository(IConnectionProvider connectionProvider, SqlConfiguration sqlConfiguration, IUnitOfWork unitOfWork, IQueryFactory queryFactory)
: base(connectionProvider, sqlConfiguration, unitOfWork, queryFactory)
{
_connectionProvider = connectionProvider;
_sqlConfiguration = sqlConfiguration;
_queryFactory = queryFactory;
_unitOfWork = unitOfWork;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use Service Locator pattern for dependencies.

As per coding guidelines, use the Service Locator pattern via Bootstrapper.GetKernel().Resolve<T>() to resolve dependencies explicitly in constructors, rather than constructor injection. Update these new repository implementations to comply with the guideline by resolving the arguments injected into the base class.

  • Repositories/Resgrid.Repositories.DataRepository/CommandDefinitionRepository.cs#L41-L48: Refactor the CommandDefinitionRoleRepository constructor to resolve dependencies via the Bootstrapper.
  • Repositories/Resgrid.Repositories.DataRepository/CommandDefinitionRepository.cs#L106-L112: Refactor the CommandDefinitionRoleUnitTypeRepository constructor to resolve dependencies via the Bootstrapper.
  • Repositories/Resgrid.Repositories.DataRepository/CommandDefinitionRepository.cs#L133-L139: Refactor the CommandDefinitionRolePersonnelRoleRepository constructor to resolve dependencies via the Bootstrapper.
📍 Affects 1 file
  • Repositories/Resgrid.Repositories.DataRepository/CommandDefinitionRepository.cs#L41-L48 (this comment)
  • Repositories/Resgrid.Repositories.DataRepository/CommandDefinitionRepository.cs#L106-L112
  • Repositories/Resgrid.Repositories.DataRepository/CommandDefinitionRepository.cs#L133-L139
🤖 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
`@Repositories/Resgrid.Repositories.DataRepository/CommandDefinitionRepository.cs`
around lines 41 - 48, The three repository constructors in
Repositories/Resgrid.Repositories.DataRepository/CommandDefinitionRepository.cs
at lines 41-48, 106-112, and 133-139—CommandDefinitionRoleRepository,
CommandDefinitionRoleUnitTypeRepository, and
CommandDefinitionRolePersonnelRoleRepository—must stop accepting injected
dependencies and resolve each dependency required by the base constructor
through Bootstrapper.GetKernel().Resolve<T>(). Preserve the existing
base-constructor dependency order and repository initialization behavior at all
three sites.

Source: Coding guidelines

Comment thread Repositories/Resgrid.Repositories.DataRepository/CommandDefinitionRepository.cs Outdated
Comment thread Web/Resgrid.Web/Areas/User/Controllers/CommandController.cs
Comment thread Web/Resgrid.Web/Areas/User/Controllers/CommandController.cs Outdated
@Resgrid-Bot

Resgrid-Bot commented Jul 14, 2026

Copy link
Copy Markdown

Code Review Could Not Complete ⚠️

The review failed before suggestions could be generated.

Reason: Transient error reaching the provider. Try again.

After fixing the issue, comment @kody review on this PR to re-run the review.

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug
Performance
Security
Business Logic

Access your configuration settings here.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
Core/Resgrid.Services/IncidentCommandService.cs (1)

647-657: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Reject assignments targeting invalid or foreign-incident lanes.

The current logic only skips the requirement evaluation if a targeted lane is invalid or belongs to another call. It leaves the foreign CommandStructureNodeId intact on the assignment object, allowing it to be persisted later in the method. This introduces cross-incident data corruption by linking a resource assignment to a lane that doesn't belong to the incident.

If an invalid or foreign lane is explicitly targeted, the assignment must be completely rejected by returning null (mirroring the safe behavior already implemented in MoveResourceAsync).

🐛 Proposed fix
 				var node = await _commandStructureNodeRepository.GetByIdAsync(assignment.CommandStructureNodeId);
-				if (node != null && node.DepartmentId == assignment.DepartmentId && node.CallId == assignment.CallId)
-				{
-					var (violation, enforced) = await EvaluateNodeRequirementsAsync(node, assignment.DepartmentId, assignment.ResourceKind, assignment.ResourceId);
-					if (violation != null && enforced)
-						throw new CommandRequirementsNotMetException(violation);
-
-					assignment.RequirementsWarning = violation != null;
-					assignment.RequirementsWarningMessage = violation;
-				}
+				if (node == null || node.DepartmentId != assignment.DepartmentId || node.CallId != assignment.CallId)
+					return null;
+
+				var (violation, enforced) = await EvaluateNodeRequirementsAsync(node, assignment.DepartmentId, assignment.ResourceKind, assignment.ResourceId);
+				if (violation != null && enforced)
+					throw new CommandRequirementsNotMetException(violation);
+
+				assignment.RequirementsWarning = violation != null;
+				assignment.RequirementsWarningMessage = violation;
🤖 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 `@Core/Resgrid.Services/IncidentCommandService.cs` around lines 647 - 657,
Update the assignment flow around _commandStructureNodeRepository.GetByIdAsync
and the node validation condition to return null immediately when an explicitly
targeted node is missing or its DepartmentId or CallId does not match the
assignment. Preserve the existing requirement evaluation for valid nodes,
including violation handling and warning fields, and mirror the rejection
behavior used by MoveResourceAsync.
🧹 Nitpick comments (1)
Core/Resgrid.Services/IncidentCommandService.cs (1)

55-76: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Resolve newly added dependencies using the Service Locator pattern.

The constructor adds IUnitsService and IPersonnelRolesService via parameter injection. As per coding guidelines, dependencies should be resolved explicitly using Bootstrapper.GetKernel().Resolve<T>() in the constructor to minimize the number of injected parameters.

♻️ Proposed refactor
-			ICoreEventService coreEventService,
-			IUnitsService unitsService,
-			IPersonnelRolesService personnelRolesService)
+			ICoreEventService coreEventService)
 		{
 			_incidentCommandRepository = incidentCommandRepository;
 			_commandStructureNodeRepository = commandStructureNodeRepository;
 			_resourceAssignmentRepository = resourceAssignmentRepository;
 			_tacticalObjectiveRepository = tacticalObjectiveRepository;
 			_incidentTimerRepository = incidentTimerRepository;
 			_incidentMapAnnotationRepository = incidentMapAnnotationRepository;
 			_commandLogEntryRepository = commandLogEntryRepository;
 			_commandTransferRepository = commandTransferRepository;
 			_commandsService = commandsService;
 			_callsService = callsService;
 			_checkInTimerService = checkInTimerService;
 			_incidentVoiceService = incidentVoiceService;
 			_incidentRoleAssignmentRepository = incidentRoleAssignmentRepository;
 			_eventAggregator = eventAggregator;
 			_coreEventService = coreEventService;
-			_unitsService = unitsService;
-			_personnelRolesService = personnelRolesService;
+			_unitsService = Bootstrapper.GetKernel().Resolve<IUnitsService>();
+			_personnelRolesService = Bootstrapper.GetKernel().Resolve<IPersonnelRolesService>();
 		}
🤖 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 `@Core/Resgrid.Services/IncidentCommandService.cs` around lines 55 - 76,
Replace the newly injected IUnitsService and IPersonnelRolesService constructor
parameters with explicit Service Locator resolution inside the constructor.
Assign both fields using Bootstrapper.GetKernel().Resolve<T>(), and leave the
existing dependency injection and field assignments unchanged.

Source: Coding guidelines

🤖 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.

Outside diff comments:
In `@Core/Resgrid.Services/IncidentCommandService.cs`:
- Around line 647-657: Update the assignment flow around
_commandStructureNodeRepository.GetByIdAsync and the node validation condition
to return null immediately when an explicitly targeted node is missing or its
DepartmentId or CallId does not match the assignment. Preserve the existing
requirement evaluation for valid nodes, including violation handling and warning
fields, and mirror the rejection behavior used by MoveResourceAsync.

---

Nitpick comments:
In `@Core/Resgrid.Services/IncidentCommandService.cs`:
- Around line 55-76: Replace the newly injected IUnitsService and
IPersonnelRolesService constructor parameters with explicit Service Locator
resolution inside the constructor. Assign both fields using
Bootstrapper.GetKernel().Resolve<T>(), and leave the existing dependency
injection and field assignments unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ff978b7f-1a66-44b1-af40-c33fc25ea58e

📥 Commits

Reviewing files that changed from the base of the PR and between 63830b4 and 1268f1e.

⛔ Files ignored due to path filters (2)
  • Tests/Resgrid.Tests/Services/CommandsServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/IncidentCommandServiceParTests.cs is excluded by !**/Tests/**
📒 Files selected for processing (8)
  • Core/Resgrid.Services/CommandsService.cs
  • Core/Resgrid.Services/IncidentCommandService.cs
  • Providers/Resgrid.Providers.Migrations/Sql/EF0001_PopulateOIDCDb.sql
  • Providers/Resgrid.Providers.Migrations/Sql/EF0003_MigrateOIDCDbToV5.sql
  • Repositories/Resgrid.Repositories.DataRepository/CommandDefinitionRepository.cs
  • Web/Resgrid.Web.Services/Controllers/v4/IncidentCommandController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/CommandController.cs
  • Web/Resgrid.Web/Areas/User/Views/Command/Index.cshtml
🚧 Files skipped from review as they are similar to previous changes (7)
  • Web/Resgrid.Web/Areas/User/Views/Command/Index.cshtml
  • Providers/Resgrid.Providers.Migrations/Sql/EF0003_MigrateOIDCDbToV5.sql
  • Providers/Resgrid.Providers.Migrations/Sql/EF0001_PopulateOIDCDb.sql
  • Web/Resgrid.Web.Services/Controllers/v4/IncidentCommandController.cs
  • Repositories/Resgrid.Repositories.DataRepository/CommandDefinitionRepository.cs
  • Core/Resgrid.Services/CommandsService.cs
  • Web/Resgrid.Web/Areas/User/Controllers/CommandController.cs

Comment thread Web/Resgrid.Web/wwwroot/js/app/internal/command/resgrid.commands.newcommand.js Outdated
@Resgrid-Bot

Resgrid-Bot commented Jul 14, 2026

Copy link
Copy Markdown

Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug
Performance
Security
Business Logic

Access your configuration settings here.

@ucswift

ucswift commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

Approve

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

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 540c023 into master Jul 14, 2026
17 of 19 checks passed
Comment on lines +41 to +42
foreach (var command in list)
await HydrateAssignmentsAsync(command);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

N+1 query in GetAllCommandsForDepartmentAsync: the loop calls HydrateAssignmentsAsync per command, and each call issues up to 3 separate DB queries (GetRolesByCommandDefinitionIdAsync, GetUnitTypesByCommandDefinitionIdAsync, GetPersonnelRolesByCommandDefinitionIdAsync), producing 3N+1 queries for N commands. Batch-load all roles, unit types, and personnel roles by command IDs in 3 fixed queries and distribute them in memory to reduce the query count to 4 regardless of command count.

Kody rule violation: Detect N+1 style queries and suggest batching

// Batch-load all child data for the department in 3 queries (not 3N):
var list = items.ToList();
var commandIds = list.Select(c => c.CommandDefinitionId).ToList();
var allRoles = (await _commandDefinitionRoleRepository.GetRolesByCommandDefinitionIdsAsync(commandIds))?.ToList() ?? new List<CommandDefinitionRole>();
var allUnitTypes = (await _commandDefinitionRoleUnitTypeRepository.GetUnitTypesByCommandDefinitionIdsAsync(commandIds))?.ToList() ?? new List<CommandDefinitionRoleUnitType>();
var allPersonnelRoles = (await _commandDefinitionRolePersonnelRoleRepository.GetPersonnelRolesByCommandDefinitionIdsAsync(commandIds))?.ToList() ?? new List<CommandDefinitionRolePersonnelRole>();

foreach (var command in list)
{
    command.Assignments = allRoles.Where(r => r.CommandDefinitionId == command.CommandDefinitionId).OrderBy(r => r.SortOrder).ToList();
    foreach (var role in command.Assignments)
    {
        role.RequiredUnitTypes = allUnitTypes.Where(u => u.CommandDefinitionRoleId == role.CommandDefinitionRoleId).ToList();
        role.RequiredRoles = allPersonnelRoles.Where(p => p.CommandDefinitionRoleId == role.CommandDefinitionRoleId).ToList();
    }
}

return list;
Prompt for LLM

File Core/Resgrid.Services/CommandsService.cs:

Line 41 to 42:

Violates rule 'Detect N+1 style queries and suggest batching': GetAllCommandsForDepartmentAsync iterates over every command definition calling await HydrateAssignmentsAsync(command) in a loop. HydrateAssignmentsAsync (line 177) issues up to 3 separate DB queries per command (GetRolesByCommandDefinitionIdAsync, GetUnitTypesByCommandDefinitionIdAsync, GetPersonnelRolesByCommandDefinitionIdAsync). For N commands this results in up to 3N+1 queries. This method is invoked from SyncService (delta sync for offline clients), the web Index page, and the v4 API list endpoint, making the N+1 impact significant on sync cycles. The fix is to batch: load all roles for all command IDs in a single query, then all unit types and personnel roles for those roles, and distribute them in memory — reducing 3N+1 queries to 4 regardless of command count.

Suggested Code:

// Batch-load all child data for the department in 3 queries (not 3N):
var list = items.ToList();
var commandIds = list.Select(c => c.CommandDefinitionId).ToList();
var allRoles = (await _commandDefinitionRoleRepository.GetRolesByCommandDefinitionIdsAsync(commandIds))?.ToList() ?? new List<CommandDefinitionRole>();
var allUnitTypes = (await _commandDefinitionRoleUnitTypeRepository.GetUnitTypesByCommandDefinitionIdsAsync(commandIds))?.ToList() ?? new List<CommandDefinitionRoleUnitType>();
var allPersonnelRoles = (await _commandDefinitionRolePersonnelRoleRepository.GetPersonnelRolesByCommandDefinitionIdsAsync(commandIds))?.ToList() ?? new List<CommandDefinitionRolePersonnelRole>();

foreach (var command in list)
{
    command.Assignments = allRoles.Where(r => r.CommandDefinitionId == command.CommandDefinitionId).OrderBy(r => r.SortOrder).ToList();
    foreach (var role in command.Assignments)
    {
        role.RequiredUnitTypes = allUnitTypes.Where(u => u.CommandDefinitionRoleId == role.CommandDefinitionRoleId).ToList();
        role.RequiredRoles = allPersonnelRoles.Where(p => p.CommandDefinitionRoleId == role.CommandDefinitionRoleId).ToList();
    }
}

return list;

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Comment on lines +270 to +273
catch (CommandRequirementsNotMetException ex)
{
Resgrid.Framework.Logging.LogException(ex);
// The target lane forces unit-type/personnel-role requirements the resource doesn't meet.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Missing error context in structured logs: the catch blocks at lines 272 and 313 call Resgrid.Framework.Logging.LogException(ex) without the extraMessage parameter, dropping identifiers like DepartmentId, assignment.IncidentCommandId, assignment.CommandStructureNodeId, input.ResourceAssignmentId, and input.TargetNodeId needed to diagnose which department, incident, or lane triggered the CommandRequirementsNotMetException.

Kody rule violation: Include error context in structured logs

catch (CommandRequirementsNotMetException ex)
{
    Resgrid.Framework.Logging.LogException(ex, $"AssignResource failed: Dept={DepartmentId}, Cmd={assignment.IncidentCommandId}, Node={assignment.CommandStructureNodeId}, Resource={assignment.ResourceId}");
Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/v4/IncidentCommandController.cs:

Line 270 to 273:

Violates rule 'Include error context in structured logs': both new catch blocks call Resgrid.Framework.Logging.LogException(ex) with no extraMessage. The LogException method (Logging.cs:65) accepts an extraMessage parameter for structured context, and other controllers in the codebase use it (e.g., InboxController passes message IDs). The catch blocks at lines 272 and 313 omit relevant identifiers such as DepartmentId, assignment.IncidentCommandId, assignment.CommandStructureNodeId (line 272), and input.ResourceAssignmentId, input.TargetNodeId (line 313) that are needed to diagnose which department, incident, or lane triggered the requirement rejection.

Suggested Code:

catch (CommandRequirementsNotMetException ex)
{
    Resgrid.Framework.Logging.LogException(ex, $"AssignResource failed: Dept={DepartmentId}, Cmd={assignment.IncidentCommandId}, Node={assignment.CommandStructureNodeId}, Resource={assignment.ResourceId}");

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

});

function addAssignment() {
var name = $.trim($('#assignment-name').val());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Inconsistent variable declaration in addAssignment(): 10 local variables (name, index, description, laneType, laneTypeName, row, nameCell, laneCell, descriptionCell, actionCell) use var while the same function body already declares forceRequirements, unitTypeIds, unitTypeNames, roleIds, roleNames, and requirementsCell with const. None of the var-declared variables are reassigned, so they should all use const.

Kody rule violation: Always use const and let

const name = $.trim($('#assignment-name').val());
...
const index = newcommand.assignmentCount++;
const description = $('#description-text').val();
const laneType = $('#assignment-lanetype').val() || '0';
const laneTypeName = $('#assignment-lanetype option:selected').text();
...
const row = $('<tr></tr>');
const nameCell = $('<td style="max-width: 215px;"></td>').text(name);
...
const laneCell = $('<td></td>').text(laneTypeName);
...
const descriptionCell = $('<td></td>').text(description);
...
const actionCell = $('<td style="text-align:center;"></td>');
Prompt for LLM

File Web/Resgrid.Web/wwwroot/js/app/internal/command/resgrid.commands.newcommand.js:

Line 28:

Violates rule 'Always use const and let': the rewritten addAssignment() function declares 10 local variables with var (name, index, description, laneType, laneTypeName, row, nameCell, laneCell, descriptionCell, actionCell). None are reassigned, so they should use const (or let for row which is captured in a closure but never reassigned). The same function already uses const for forceRequirements, unitTypeIds, unitTypeNames, roleIds, roleNames, and requirementsCell, making the var usage inconsistent within the same function body.

Suggested Code:

const name = $.trim($('#assignment-name').val());
...
const index = newcommand.assignmentCount++;
const description = $('#description-text').val();
const laneType = $('#assignment-lanetype').val() || '0';
const laneTypeName = $('#assignment-lanetype option:selected').text();
...
const row = $('<tr></tr>');
const nameCell = $('<td style="max-width: 215px;"></td>').text(name);
...
const laneCell = $('<td></td>').text(laneTypeName);
...
const descriptionCell = $('<td></td>').text(description);
...
const actionCell = $('<td style="text-align:center;"></td>');

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

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.

2 participants