Skip to content

Visually Group User Visits with Work Areas on Microplanning Map#1144

Open
zandre-eng wants to merge 7 commits intomainfrom
ze/visit-work-area-grouping
Open

Visually Group User Visits with Work Areas on Microplanning Map#1144
zandre-eng wants to merge 7 commits intomainfrom
ze/visit-work-area-grouping

Conversation

@zandre-eng
Copy link
Copy Markdown
Contributor

@zandre-eng zandre-eng commented Apr 23, 2026

Product Description

When a work area is selected on the microplanning map, its linked user visits are now highlighted as green diamonds rendered on top of the existing red dots. All unrelated visits simultaneously fade to 20% opacity so the selected work area's visits visually pop out. Clearing the selection returns visits to their normal red-dot appearance.

An example of a work area selected, showing green diamonds overlaid on its visits with other visits dimmed:
Screenshot_20260424_151449

An example of the default state (no selection) for comparison:
Screenshot_20260423_131606

This only applies in normal mode — assignment mode does not support showing user visits and so highlighting of user visits does not apply to that mode.

Technical Summary

Link to ticket here

This is a release path 3 feature — Experiments & early stage iterations of product area.

Implementation is frontend-first with one small backend change. The existing selectedFeature in the mapController Alpine component remains the single source of truth; a derived highlightedWorkAreaId (synced via a $watch on selectedFeature) drives styling on two Mapbox layers. A symbol layer type is required for the diamond shape because Mapbox's circle layer can only render circles.

  • Backend — expose work_area_id on visit tiles: UserVisitVectorLayer now includes work_area_id in tile_fields and in the queryset values so the client can filter features without re-fetching tiles.
  • Client — derived highlight state: new Alpine field highlightedWorkAreaId, updated by updateHighlightedWorkAreaId() which returns null in assignment mode or when nothing is selected, otherwise the selected feature's id. Wired via $watch('selectedFeature', ...).
  • Client — hidden symbol layer: new user-visits-highlight layer (symbol type, text-field, green fill, white halo) added alongside user-visits-circle. Added via addVisitHighlightLayer() when visits are first shown; starts hidden.
  • Client — styling driver: new refreshVisitStyling(), wired via $watch('highlightedWorkAreaId', ...), sets circle-opacity on the base circles (0.2 dimmed / 0.7 default) and toggles + filters the highlight layer (["==", ["get", "work_area_id"], id]).
  • Client — paint builder extraction: circle paint config was extracted into buildVisitCirclePaint() so layer creation and styling refresh stay in sync.

Safety Assurance

Safety story

This change is purely additive:

  • Backend change is a no-op for existing consumers: tile_fields and .values() additions ship one extra column (work_area_id) in tiles. Existing clients that don't reference the property ignore it.
  • No migrations, no data mutations, no new endpoints, no permission changes.
  • New layer and state are gated on showVisits and start hidden; in the default (no-selection) state the map looks and behaves exactly as before.
  • Assignment mode is explicitly excluded from the highlight logic via updateHighlightedWorkAreaId, so the assignment-mode UX is unaffected.
  • Reversible by revert — no persisted state.

Automated test coverage

  • TestUserVisitVectorLayer.test_queryset_annotates_location_point in commcare_connect/microplanning/tests/test_views.py was extended to assert visit["work_area_id"] == visit_data.work_area.id. Catches regressions in tile_fields or the queryset .values() that would strip work_area_id from emitted tiles.

Frontend interaction changes are verified manually (see QA Plan).

QA Plan

QA will not be performed for this change. Below is the testing plan for reference:

  • Open the microplanning map for an opportunity that has user visits and confirm the baseline state: red dots at 0.7 opacity, no diamonds visible.
  • Toggle "Show Visits" off and on; confirm visits appear/disappear without errors and styling state is preserved.
  • Click a work area that has linked visits; confirm:
    • Green diamonds appear on exactly the visits whose work_area_id matches the selected work area.
    • All other visit circles dim to ~0.2 opacity.
    • Diamonds are visibly larger than the base circles at typical zoom levels (zoom 8 → 14).
  • Click the same work area again to deselect; confirm diamonds disappear and circles return to 0.7 opacity.
  • Click a different work area; confirm the diamond set updates to match the new selection (no stale diamonds from the previous selection).
  • Click a work area with no linked visits; confirm circles dim but no diamonds appear.
  • Change filters while a work area is selected; confirm the tile reload preserves correct diamond/dim state for the new feature set.
  • Enter assignment mode, multi-select several work areas; confirm no diamond highlighting and no circle dimming occurs (assignment mode is excluded).
  • Exit assignment mode back to normal mode; confirm highlight behavior resumes on next single-select.
  • Zoom in and out across the visit zoom range; confirm diamonds remain legibly sized and aligned over their circles.

Labels & Review

  • The set of people pinged as reviewers is appropriate for the level of risk of the change

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

Walkthrough

The pull request adds work-area attribution to the user-visit MVT tiles and implements corresponding client-side visualization. The backend changes extend the UserVisitVectorLayer queryset to include work_area_id in the tile data and update tile_fields to expose this attribute. The frontend adds a state variable highlightedWorkAreaId derived from the selected feature, uses it to dynamically adjust the opacity of the user-visits circle layer, and introduces a new highlight symbol layer filtered by work-area ID. A test assertion verifies that the queryset annotation includes the expected work_area_id value.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: visually grouping user visits with work areas on the microplanning map through highlighting and opacity adjustments.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description check ✅ Passed The PR description clearly describes the feature implementation with product context, technical details, and safety assurances aligned to the changeset.

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

✨ 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 ze/visit-work-area-grouping

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: 1

🧹 Nitpick comments (1)
commcare_connect/templates/microplanning/map_handler.html (1)

45-59: Minor: buildVisitCirclePaint() duplicates opacity logic with refreshVisitStyling().

hasSelection ? 0.2 : 0.7 is set here at layer creation and then immediately re-applied by refreshVisitStyling() on line 651. Consider dropping circle-opacity from buildVisitCirclePaint() (or having refreshVisitStyling() be the single source of truth) to avoid drift if the thresholds change.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@commcare_connect/templates/microplanning/map_handler.html` around lines 45 -
59, buildVisitCirclePaint currently hardcodes 'circle-opacity' (hasSelection ?
0.2 : 0.7) which duplicates the logic applied later in refreshVisitStyling;
remove the 'circle-opacity' entry from buildVisitCirclePaint so
refreshVisitStyling remains the single source of truth for opacity, and ensure
refreshVisitStyling still sets the correct opacity based on
highlightedWorkAreaId (no other callers rely on the removed property).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@commcare_connect/templates/microplanning/map_handler.html`:
- Around line 88-111: refreshVisitStyling() currently makes
'user-visits-highlight' visible whenever highlightedWorkAreaId is non-null,
ignoring this.showVisits; update the logic in refreshVisitStyling to only set
the 'user-visits-highlight' visibility to 'visible' when this.showVisits is true
and highlightedWorkAreaId is not null, otherwise set visibility to 'none' (keep
the existing filter behavior for setting the layer filter by work_area_id).
Ensure the check uses the component property this.showVisits and the layer name
'user-visits-highlight' so toggling Show Visits always keeps highlights hidden.

---

Nitpick comments:
In `@commcare_connect/templates/microplanning/map_handler.html`:
- Around line 45-59: buildVisitCirclePaint currently hardcodes 'circle-opacity'
(hasSelection ? 0.2 : 0.7) which duplicates the logic applied later in
refreshVisitStyling; remove the 'circle-opacity' entry from
buildVisitCirclePaint so refreshVisitStyling remains the single source of truth
for opacity, and ensure refreshVisitStyling still sets the correct opacity based
on highlightedWorkAreaId (no other callers rely on the removed property).
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ab8b896c-1733-4872-bcb0-3129e0997e45

📥 Commits

Reviewing files that changed from the base of the PR and between 1eb3aac and 9948e34.

📒 Files selected for processing (3)
  • commcare_connect/microplanning/tests/test_views.py
  • commcare_connect/microplanning/views.py
  • commcare_connect/templates/microplanning/map_handler.html

Comment thread commcare_connect/templates/microplanning/map_handler.html
@zandre-eng zandre-eng force-pushed the ze/visit-work-area-grouping branch from 9948e34 to f6b48a1 Compare April 23, 2026 11:39
Copy link
Copy Markdown
Contributor

@pxwxnvermx pxwxnvermx left a comment

Choose a reason for hiding this comment

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

Just a small nit. Rest of the changes look good to me.

8, 13,
14, 22,
],
'text-font': ['DIN Pro Medium', 'Arial Unicode MS Regular'],
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.

nit: Can these be replaced with commonly available fonts or browser generics like "Sans", "Serif" etc?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I got the following response after investigating it with Claude:

text-font here isn't a CSS font — Mapbox GL renders text from SDF glyphs served by the style's glyphs endpoint, so the value has to match a font the active style actually ships. Generics like Sans/Serif aren't resolved and the layer would fail to render glyphs. DIN Pro Medium + Arial Unicode MS Regular are the fonts bundled with the mapbox://styles/mapbox/standard style we're using (Arial Unicode is the standard CJK fallback). Happy to swap to another Mapbox-hosted stack if you'd prefer, but browser generics won't work here.

Given the above, I'm opted to keep the current font as it ships with the Mapbox style. For verification I did switch it to Sans font and received a 404 error in the browser's dev console which confirms that generic fonts won't work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zandre-eng zandre-eng requested a review from pxwxnvermx April 24, 2026 13:15
@mkangia
Copy link
Copy Markdown
Contributor

mkangia commented Apr 28, 2026

💠

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.

3 participants