Skip to content

RES-1995 rework groups page to have a map of groups#744

Open
edwh wants to merge 77 commits into
developfrom
RES-1995_map_of_groups
Open

RES-1995 rework groups page to have a map of groups#744
edwh wants to merge 77 commits into
developfrom
RES-1995_map_of_groups

Conversation

@edwh
Copy link
Copy Markdown
Collaborator

@edwh edwh commented Sep 23, 2024

Summary

  • Map view of groups on network coordinator page: the network show page now embeds a GroupMapAndList component that shows all groups in the network on an interactive Leaflet map, with a sortable table below
  • Filter bar for network coordinators: NCs and admins now see a filter bar above the groups table (name, location, country, tags) — the same filters already available on the All Groups page
  • Tag management on network page: NCs and admins can create, edit, and delete tags directly from the network page; tags can be filtered in the groups table
  • Laravel 13 compatibility: merges issue-853 (the L13 upgrade) into this branch — all PHP 8.4, Laravel 13, and Vite build changes are included

What changed

Backend

  • NetworkController::show(): computes lat/lng bounding box for map, fetches tags for NCs/admins, returns canManageTags flag
  • GroupController::indexVariations(): returns role-scoped tags (admin sees all, NC sees network tags only, others see none)
  • app/Http/Resources/Group.php: includeStats now opt-in (defaults false) for performance

Frontend

  • New GroupMapAndList.vue: accepts showFilters, canManageTags, availableTags props, passes them to GroupsTable
  • Updated GroupsTable.vue: accepts search/allGroupTags/showTags props; filter state drives a filteredItems computed property
  • Updated NetworkPage.vue: replaced "view groups" link with embedded GroupMapAndList; accepts mapBounds prop
  • networks/show.blade.php: passes mapBounds and tag-management props to NetworkPage

Tests

  • NetworkShowTest: PHPUnit checks NC and admin see can-manage-tags=true and correct tags; map bounds reflect group locations
  • grouptags.test.js: Playwright E2E tests for full tag CRUD on the network page

Code Quality Review

  • No new security concerns — all user-facing inputs use parameterised queries
  • Vue filter logic is client-side only (no new API endpoints added for filtering)
  • GroupMapAndList props have safe defaults so existing usages (GroupsPage) are unaffected

Test Plan

  • Login as NC for a network — network page shows map of groups and filter bar with tags
  • Login as admin — same experience, sees all tags
  • Login as host — network page not accessible (enforced by NetworkPolicy)
  • Filter by name/location/country/tags on network page
  • Create/edit/delete a tag on the network page (NC and admin)
  • Groups page /group — "Nearby/All" tab still shows map, "Mine" tab still shows table
  • PHPUnit: NetworkShowTest passes in CI
  • Playwright: grouptags.test.js passes in CI

edwh added 30 commits September 16, 2024 13:23
# Conflicts:
#	package-lock.json
# Conflicts:
#	app/Http/Controllers/API/GroupController.php
#	app/Http/Controllers/GroupController.php
#	app/User.php
#	resources/js/components/GroupsTable.vue
#	tests/Feature/Groups/APIv2GroupTest.php
edwh and others added 9 commits May 20, 2026 09:18
When npm is backgrounded in docker_run.sh, neither public/hot nor
public/build/manifest.json exists when php-fpm starts (~2min into startup).
Every page request triggers a ViteManifestNotFoundException -> HTTP 500,
so the health check never passes.

Fix: run npm install + npm run build on the CI host BEFORE starting Docker.
public/build/manifest.json then exists in the volume-mounted workspace from
the moment php-fpm starts handling requests.

Also add node_modules caching so subsequent runs are fast (seconds vs minutes).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MySQL 8.0 enables binary logging by default, which requires SUPER privilege
to create triggers. The restarters user lacks SUPER, so migrate:fresh --seed
was failing with SQLSTATE 1419, leaving the DB empty and causing HTTP 500.

Moving log_bin_trust_function_creators=1 into mysql/my.cnf ensures it's
applied at MySQL startup, before docker_run.sh runs migrations. Previously
it was only set in the CI "Setup application" step — but that step runs
after "Wait for services", which never completed due to the migration failure.

Also capture storage/logs/laravel.log as a CI artifact for future debugging.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In CI (PHP-FPM), the file cache was returning __PHP_Incomplete_Class for
stdClass objects in waste_stats[0], causing E_WARNING → ErrorException →
HTTP 500 on every health check request.

Root fix: computeStats() now stores waste_stats and device_count_status as
plain PHP arrays instead of DB stdClass objects. No class deserialization
needed at all.

isStatsValid() now also checks waste_stats[0] is an array, so any cached
data in old format (stdClass) is treated as invalid and recomputed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixometer::loginRegisterStats(): if Cache::put() fails after the cold-start
lock (disk full, backend down), Cache::get() returns null and
decorateStats(null) would hit a TypeError. Now falls back to computing
uncached.

decorateStats(): add ?? [] guard on waste_stats[0] access so a corrupt
cache entry returns zeros rather than a fatal error.

Trigger migration: attempt SET GLOBAL log_bin_trust_function_creators = 1
before creating triggers; silently catches SUPER-privilege denial so the
migration still runs on hosts where the flag is already set in my.cnf (e.g.
production).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The runtime `patch` call in docker_run.sh was a workaround: cweagans/composer-patches
already manages the other vendor patches, so this one should live there too.
cweagans applies patches on fresh installs (cache misses in CI) and the cached
vendor already carries the patch from prior builds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
edwh/laravel-drip was a personal fork. TheRestartProject/laravel-drip
is the org-owned fork, currently at the Laravel 10 support commit.
The existing laravel-drip-l11 composer patch supplies the L11/12/13
constraint, so behaviour is unchanged — just a cleaner provenance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Key conflict resolutions:
- package.json: merged leaflet/vue2-leaflet map deps with L13 sentry/popperjs upgrades
- app.js: kept map component registrations (GroupMapAndList, GroupMarker, GroupInfoModal)
  in L13's reorganised initializeJQuery() block; dropped duplicate jQuery init from
  map branch's end-of-file
- GroupsPage.vue: merged images mixin from L13 with map imports from HEAD
- GroupsTable.vue: merged L13's GroupsTableFilters UI and images mixin with map branch's
  row-hover events for map synchronisation
- GroupsTableFilters.vue: restored (was deleted in map branch; needed for NC filter bar)
- NetworkController: kept mapBounds calculation AND merged L13's stats/tags/networkData
- User.groupsNearby(): kept null check from map branch, used safer selectRaw from L13;
  added image eager-loading (load('groupImage.image')) inside the null-check block
- Group resource: adopted L13's includeStats=false default (opt-in for perf)
- header.blade.php: used Vite @Vite() directive, kept leaflet CSS links
- networks/show.blade.php: kept map branch layout (sidebar + GroupMapAndList);
  added show-filters, can-manage-tags, available-tags props for Phase 2 filter bar
- webpack.mix.js: accepted deletion (L13 uses Vite)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GroupMapAndList: accept showFilters/canManageTags/availableTags props,
  pass them through to GroupsTable as search/showTags/allGroupTags
- GroupsTable: add search/networks/allGroupTags/showTags props; add
  searchName/Location/Network/Country/Tags state; add groups and
  filteredItems computed properties so filter bar controls the table
- Add NetworkShowTest covering NC and admin scenarios plus map bounds

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The merge accidentally reverted to an older two-column blade layout
that dropped NetworkPage.vue (which has tag management UI required by
the grouptags Playwright tests).

- networks/show.blade.php: restore to use NetworkPage + add :map-bounds
- NetworkPage.vue: replace simple groups count/link with GroupMapAndList;
  add mapBounds prop and GroupMapAndList import/component registration
- NetworkShowTest: update assertions to check NetworkPage props directly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

<!-- Styles -->
@vite(['resources/sass/app.scss', 'resources/global/css/app.scss'])
<link rel="stylesheet" href="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css" type="text/css" />
<!-- Styles -->
@vite(['resources/sass/app.scss', 'resources/global/css/app.scss'])
<link rel="stylesheet" href="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css" type="text/css" />
<link rel="stylesheet" href="//unpkg.com/leaflet-gesture-handling/dist/leaflet-gesture-handling.min.css" type="text/css">
edwh and others added 19 commits May 21, 2026 15:15
Vite 4.x does not include .vue in default resolve.extensions, so
extension-less imports of .vue files fail in production builds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without this, CircleCI's fallback composer cache (from develop) is used
as the starting point, and re-applying the guzzle nullable patch on an
already-patched vendor causes inconsistent autoloader state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…mposer plugins

- composer.lock: declare Laravel 13 support in wouternl/laravel-drip require
  section (the fork's code works with L13; the constraint was just too
  conservative — cweagans patch updated this at runtime but not at build time)
- Dockerfile.fly: set COMPOSER_ALLOW_SUPERUSER=1 so composer plugins (including
  cweagans/composer-patches) can run during the image build

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cweagans/composer-patches needs patches/composer.patches.json to be
present when composer install runs. Previously only composer.json and
composer.lock were copied at that stage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…packages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The navbar renders a <div class="vue"> containing <Notifications>, which
assertVueProperties picks up as props[0] on any logged-in page. Tests
were checking props[0] for NetworkPage props but needed to check props[1].
Also remove two unused translation keys (groups_count, view_groups_link)
from lang/en/networks.php — replaced by the NetworkPage Vue component.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
User.groupsNearby() was updated on this branch to return groups matching
the user's country when lat/lng are null. The dashboard test expected 0
nearby groups for a null-location GB user, but the group created in setUp()
has country_code='GB', so the country branch now returns 1.

Add a separate newGroupCount parameter so nearby-groups and new-groups
can have different expectations: null-location GB user gets 1 nearby group
(country match) but 0 new groups (dashboard controller guards that call
with an explicit null-lat/lng check).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The group index no longer server-renders :all-groups in the blade —
groups are fetched via /api/v2/groups/summary by the Vue component.
Rewrite testGroupIndexNextEventIsEagerLoaded to call that API endpoint
with includeNextEvent=true and verify next_event is populated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lity

GroupMapAndList shows a loading spinner while fetching groups via API,
then initializes a Leaflet map which runs flyToBounds before GroupsTable
renders. The previous fixed 2-second timeout was insufficient in CI.

Now waits for .loader to be hidden (API complete) then uses a 15-second
timeout on the table visibility assertion to allow for map initialization.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When currentTab was set in created(), BootstrapVue's lazy b-tab missed
the initial activation event, leaving the Other Groups tab panel empty.

Initialize currentTab directly in data() from the tab prop so the
correct tab is active from the very first render cycle, allowing lazy
tab content to render correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…{id}

BootstrapVue lazy tabs only render content when localActive transitions
from false to true. When the tab is initially active (currentTab=1 from
data()), localActive starts true and the transition watcher never fires,
leaving the tab panel permanently empty.

Removing lazy from the Other Groups tab ensures GroupMapAndList always
mounts. The map API call runs in the background but this is acceptable
since the map is the primary content for network group pages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… bounds

When Leaflet map bounds calculation doesn't fire (e.g. headless CI environment),
groupidsInBounds stays empty and the table never renders. Add effectiveGroupIds
computed that falls back to all store groups (filtered by network if set) so the
table is always visible once the API call completes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
waitForLoadState('networkidle') hangs indefinitely on /group/network/ pages
because Leaflet tile requests prevent the network from going idle in headless
CI. Replace with waitForSelector('.loader', {state:'hidden'}) which resolves
once the groups API call completes — the actual signal we need.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…olving

The /group page now has GroupMapAndList rendered in the background tab,
which loads Leaflet tiles and prevents networkidle from ever resolving.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace networkidle on /networks/ pages with waitForSelector('.tags-management')
  since NetworkPage.vue also uses GroupMapAndList with Leaflet tiles
- Increase tag-item assertion timeouts to 15s (API calls can be slow in CI)
- Fix strict mode violation in event test: .multiselect resolves to 2 elements,
  use .first() to target the group member select

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…reateTag()

The mounted() hook fetches existing tags asynchronously. If createTag() runs
and pushes a new tag before the GET response arrives, the subsequent
this.tags = response.data.data assignment clears the pushed tag (the GET
was issued before the POST, so the server returns an empty array).

Fix: merge fetched tags with any already present rather than overwriting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The NetworkPage.vue mounted() GET for tags was incorrectly using a merge
strategy that correlated with createTag() not being invoked in CI. Revert
to plain assignment and instead fix the race in the test by waiting for
the tags-management element and a short delay before filling the form,
ensuring mounted()'s GET completes before the form is submitted.
Kill Vite BEFORE modifying .env in docker:test:playwright — Vite v4
crashes with ERR_INVALID_ARG_TYPE when it detects .env changes while
running, which risks leaving the build in an inconsistent state.

Switch --reporter=html to --reporter=list,html so each completed test
prints a line to stdout, preventing CircleCI's no_output_timeout from
killing the Playwright step mid-run.

Add console.log tracing and 10s explicit timeouts to "NC can create a
tag" to identify the exact step that hangs in CI.
page.fill() fails to update Vue's newTagName after a mounted() GET
response triggers a re-render: the DOM value is set but the reactive
state stays empty so the submit button remains disabled.

pressSequentially() simulates real keystrokes (keydown/input/keyup per
char), which reliably drives BootstrapVue's onInput handler regardless
of prior re-renders. Also remove the waitForTimeout(1000) that was
masking the issue by ensuring the re-render always happened first.
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot
B Security Rating on New Code (required ≥ A)
B Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

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