Add property picker for quick-add to day detail page #241
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "237-day-property-picker"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds a property picker to the day detail page so admins can quickly add properties to a day's work queue without leaving the page. Reuses the existing
property_picker_controller.jsStimulus controller and follows the same patterns established in the Today page.Changes
config/routes.rb-- AddedPOST /days/:date/addroute mapped todays#add_to_queueapp/controllers/days_controller.rb-- Added@propertiesand@queued_property_idstoshowaction; addedadd_to_queueaction that creates a WorkQueueItem, handles duplicates gracefully, and responds with turbo_streamapp/views/days/show.html.erb-- Added property picker form above the queued list, reusing the same markup pattern and Stimulus controller as the Today page; added DOM IDs for turbo stream targetingapp/views/days/add_to_queue.turbo_stream.erb-- New turbo stream response that appends the new queue item to the list and removes the empty statespec/requests/days_spec.rb-- Added 15 new specs: property picker rendering (3), add_to_queue functionality (5), and role access for add_to_queue (5)Test Plan
Review Checklist
property_picker_controller.jswithout modificationrequire_role :admin, :super_admin@queue_itemsuses.includes(:property)Related Notes
Related
Self-Review Findings
Correctness
PASS -- All 25 specs pass (10 existing + 15 new). Route, controller, view, and turbo stream all work together correctly.
Potential Issues
Dead code in
add_to_queue(minor) -- Lines 42-43 load@queue_itemsand@queued_property_idsafter a successful save, but the turbo stream template (add_to_queue.turbo_stream.erb) does not use either variable. These were loaded anticipating future use (e.g., updating the picker dropdown to mark the property as queued), but the current Stimulus controller handles that client-side inselect(). Verdict: harmless -- two cheap queries that go unused. Could be removed but also provides a hook if the turbo stream is extended later.No new-property-via-Enter support -- The
client_namehidden field is present in the form (required by the Stimulus controller'ssubmitaction), butadd_to_queueonly handlesproperty_id. If a user types a name and hits Enter,property_idwill be empty and the create will fail with a validation error. Verdict: acceptable -- the Today page supports this but the day detail page is an admin tool for scheduling existing properties, not creating new ones. The error path handles it gracefully (422 with flash).N+1 queries -- No issues.
showuses.includes(:property)on queue items.add_to_queueaccesses@item.propertyonce (single record, not a loop).Property.active.order(:client_name)is a single query.Race condition on duplicate check -- The
find_by+savepattern has a small window where two concurrent requests could both pass the duplicate check. The model'svalidates :property_id, uniqueness: { scope: :work_date }and the DB unique index catch this, and the error path returns a proper 422. Verdict: safe.Summary
No blockers. Ready for QA review.
PR #241 Review
DOMAIN REVIEW
Tech stack: Ruby on Rails 8.1, Hotwire (Turbo Streams + Stimulus), RSpec request specs.
Controller pattern (app/controllers/days_controller.rb)
The
add_to_queueaction follows the establishedWorkQueueItemsController#createpattern well:find_bybefore save -- good UX over raw model validation errorsvalidates :property_id, uniqueness: { scope: :work_date }) provides the safety netrespond_towith both turbo_stream and HTML fallback -- correctrequire_role :admin, :super_admin-- verified inapplication_controller.rbparse_datereturns nil for invalid dates, and the guard clause redirects -- correctStimulus controller reuse
The view correctly wires all six targets expected by
property_picker_controller.js:input,dropdown,option,form,propertyId,clientName. The data-action bindings (focus->open,input->filter,keydown->submit,click->select) match the controller. Thedata-queued,data-property-id, anddata-search-textattributes are all present and correctly populated. Good reuse without modification.Turbo stream response (add_to_queue.turbo_stream.erb)
Appends to
day-queue-listand removesday-queue-empty-- both IDs are present in the view. Markup structure mirrors the existing queue item rendering inshow.html.erb.Accessibility
Good ARIA support:
role="combobox"on input,aria-expanded,aria-controlspointing today-picker-listbox,aria-autocomplete="list",role="listbox"on the dropdown,role="option"on each item. This matches WAI-ARIA combobox patterns.BLOCKERS
None.
NITS
Stale heading count after turbo_stream add. The
<h3>Queued (N)</h3>heading is only rendered when@queue_items.any?and shows a static count. After a turbo_stream append, the count remains at the original value (or the heading is absent if the list was empty). Consider wrapping the heading in a turbo frame or adding aturbo_stream.updatefor the heading inadd_to_queue.turbo_stream.erb. Low priority since users see the item appear in the list, but the count becoming stale is a minor UX gap.Position race condition.
WorkQueueItem.where(work_date: @date).countfor setting position is not atomic -- two concurrent requests could get the same position value. This is the same pattern used inWorkQueueItemsController#create(line 85), so it is consistent with existing code, but worth noting as a future improvement (e.g.,MAX(position) + 1in a transaction, or a DB default).No spec for invalid property_id. There is no test for what happens when
property_idrefers to a non-existent property. Thebelongs_to :propertywill raiseActiveRecord::InvalidForeignKeyon save. Consider adding a spec to document the expected behavior (likely a 500, which the app's error handling should catch). Not a blocker since this is an admin-only endpoint and the picker UI only provides valid IDs.No spec for unauthenticated POST. The role access specs test authorized vs unauthorized roles, but there is no spec confirming that an unauthenticated user hitting
POST /days/:date/addgets redirected to login. The existingGET /days/:datespecs also omit this, so this is consistent, but worth noting.client_namehidden field is inert. The form includes<input name="client_name">for Stimulus controller compatibility, but theadd_to_queueaction only readsparams[:property_id]. The PR body documents this -- good. If someone presses Enter to create a new property, the action will silently ignoreclient_nameand attempt to save with a nilproperty_id. This is safe (save fails) but could be confusing. A flash message explaining "property selection required" would improve the UX if this is ever triggered.SOP COMPLIANCE
PROCESS OBSERVATIONS
VERDICT: APPROVED
PR #241 Review
DOMAIN REVIEW
Stack: Ruby on Rails 8.1, Hotwire (Turbo Streams + Stimulus), RSpec request specs.
Pattern consistency with WorkQueueItemsController#create:
The new
DaysController#add_to_queuecorrectly follows the established pattern: check for duplicate, create with position, respond with turbo_stream on success and flash-messages append on failure. The simplification relative to the Today controller is appropriate -- the day detail page does not need thecompleted_by_otherreclaim path orclient_namecreation path, and the PR body explicitly calls this out.Stimulus controller reuse:
The property picker markup in
show.html.erbcorrectly targets all required Stimulus targets:input,dropdown,option,form,propertyId,clientName. TheclientNamehidden field is present for controller compatibility even though the action only handlesproperty_id-- this is documented in the PR body ("Related Notes"). Theselect()method inproperty_picker_controller.jswill correctly setdata-queued="true"and add the badge on click, preventing re-selection.Turbo Stream response:
add_to_queue.turbo_stream.erbappends today-queue-listand removesday-queue-empty. The DOM IDs are unique (day-queue-list,day-queue-empty,day-picker-listbox) and will not collide with the Today page's IDs. Good.N+1 queries:
The
showaction uses.includes(:property)on queue items. The@propertiesquery usesProperty.active.order(:client_name)without eager-loading associations, which is correct -- the picker only needsclient_name,address_line, andcity, all of which are on thepropertiestable directly.Model validation:
WorkQueueItemhasvalidates :property_id, uniqueness: { scope: :work_date }. The controller's explicitfind_bycheck beforesaveis belt-and-suspenders -- avoids a race condition producing a 500 from the uniqueness constraint. Thefind_byreturns a graceful 422 flash instead. Sound approach.BLOCKERS
None.
Input validation check --
property_id:The
property_idparam is passed directly toWorkQueueItem.new(property_id: property_id). This is a foreign key toproperties. If an attacker sends a non-existentproperty_id, thebelongs_to :propertyassociation inWorkQueueItemwill raiseActiveRecord::InvalidForeignKeyat the database level, which Rails rescues as a 500. However, this endpoint is gated behindrequire_role :admin, :super_admin, so only trusted users can reach it. This is not a security blocker but is noted as a nit below.Test coverage:
15 new specs covering: property picker rendering (3), add_to_queue functionality (5), role access for add_to_queue (5). This covers happy path, duplicate handling, turbo stream response format, position assignment, HTML fallback, and all five roles. No blocker.
NITS
Defensive
property_idlookup (app/controllers/days_controller.rb,add_to_queueaction): Consider wrapping the property lookup withProperty.find_by(id: params[:property_id])and returning a 404/422 if nil. Currently, a non-existentproperty_idwill raiseActiveRecord::InvalidForeignKeyat the DB level. TheWorkQueueItemsController#createhas the same pattern, so this is a pre-existing codebase choice rather than a regression, but worth noting.Queuedcount header not updated by turbo stream (app/views/days/show.html.erb): The<h3 class="section-heading">Queued (<%= @queue_items.size %>)</h3>is rendered server-side and not wrapped in a turbo frame or targeted by the turbo stream response. After adding a property via the picker, the count in the header will be stale until a full page reload. The Today page likely has the same limitation, so this may be intentional, but noting it for awareness.Empty state reappearance on remove (future consideration):
turbo_stream.remove "day-queue-empty"handles the case where the list transitions from empty to non-empty. The reverse (removing the last item to show empty state) is not in scope for this PR since remove-from-list is ticket #236. Just noting the complementary pattern will be needed there.positionrace condition (app/controllers/days_controller.rb, lineposition: WorkQueueItem.where(work_date: @date).count): If two admins add items simultaneously, they could get the same position value. This is a pre-existing pattern fromWorkQueueItemsController#create(line 85) and not a regression. Low risk given the small user base.SOP COMPLIANCE
PROCESS OBSERVATIONS
showaction (@queued_property_idsand@properties).VERDICT: APPROVED