Skip to content

Create Vision Zero incidents list page - #2074

Open
johnclary wants to merge 60 commits into
mainfrom
john/28753-vz-incident-list
Open

Create Vision Zero incidents list page#2074
johnclary wants to merge 60 commits into
mainfrom
john/28753-vz-incident-list

Conversation

@johnclary

@johnclary johnclary commented Jun 30, 2026

Copy link
Copy Markdown
Member

Associated issues

This PR adds a basic VZ incident listing backed by a materialized view. The page is only visible to editors and admins.

Testing

  1. You'll want to replicate from prod if you haven't done so in the last week-ish to make sure you've got the latest VZ incidents.
  2. Apply migrations and metadata: Clock how long it takes to apply this migration. We might want to consider manually applying this materialized view and using a placeholder statement in the migration itself.
  3. Start your VZE
  4. Use the side nav to navigate to the new /incidents list page. It should load normally. Test all the things:
  • Sort by address column
  • Show/hide columns using the settings menu (right side of table controls)
  • Use the date filter presets, and also the custom data range
  • Search by address, other columns
  • Use the advanced filter toggles (filter by agency, has/does not have crash report, jurisdiction)
  1. Use the toggle to switch to the map view, click around on some features. Use the hyperlinked incident ID to visit the incident details page
  2. Confirm the incident details page has a breadcrumb and renders text like Incident details page for ID #<some-number>.
  3. Log out, then log in with the credentials called Vision Zero Editor (VZE) Test User: Read-only viewer and confirm that the Incident page is hidden from the side nav.

Ship list

  • Check migrations for any conflicts with latest migrations in main branch
  • Confirm Hasura role permissions for necessary access
  • Code reviewed
  • Product manager approved

@netlify

netlify Bot commented Jun 30, 2026

Copy link
Copy Markdown

Deploy Preview for vzv-staging canceled.

Name Link
🔨 Latest commit 257243b
🔍 Latest deploy log https://app.netlify.com/projects/vzv-staging/deploys/6a6234ada3152800096cee29

@netlify

netlify Bot commented Jun 30, 2026

Copy link
Copy Markdown

Deploy Preview for vze-staging canceled.

Name Link
🔨 Latest commit 257243b
🔍 Latest deploy log https://app.netlify.com/projects/vze-staging/deploys/6a6234ad4a2bf40008b955ff

@johnclary
johnclary changed the base branch from main to john/incident-etl-redux June 30, 2026 18:00
@johnclary
johnclary changed the base branch from john/incident-etl-redux to main July 10, 2026 19:15
@johnclary johnclary changed the title Create Vision Zero incidents list Create Vision Zero incidents list page Jul 10, 2026
properties: GeoJsonProperties;
}

export default function VzMapPopupContent({

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Image

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I liked searching for Burnet and then toggling the map and seeing most of the incidents lining up with the street, except one or two. The future incident details page is gonna be great

id: "other",
column: "responding_agencies_str",
operator: "_nlike",
value: "%$apd$%",

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Here's that string-concatenated field coming into play: this is how we find non-apd crashes.

Image

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.

nice thanks for connecting the earlier comment to this one. it was a good reminder about why we do a similar thing for team members, etc. in the Moped advanced search 🙏

id: "has_crash_report",
column: "record_tables_str",
operator: "_ilike",
value: "%$crashes$%",

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

And here's how we find incidents that don't have a crashes record—aka there is no crash report.

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.

An incident cant both have a crash report and not have one, but you can toggle both on as if thats possible. Would it be a big lift to make it so you can only toggle one at a time?

@johnclary johnclary Jul 23, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It would be a lift, yeah. FWIW this behavior is consistent with our column visibility toggles as well 🤷‍♂️

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.

okay thats fine then! i figured it would be a big lift

* Name of the geojson transformer function to use when converting the input table to geojson
*/
geojsonTransformerName: "latLon";
geojsonTransformerName: "latLon" | "pointFeature";

@johnclary johnclary Jul 21, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This lets us consume the geojson Point that comes out of the vz_incident_records_view rather than reconstructing it via lat/lon. Sorta feels like to the right way to have done this from the beginning.

Comment thread editor/configs/routes.ts
path: "incidents",
label: "Incidents",
icon: FaRoad,
allowedRoles: ["editor", "vz-admin"],

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Only editors and admins will be able to see this view while we're in this alpha stage of development

@mddilley mddilley Jul 22, 2026

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.

When I was testing, I saw that the viewer cannot see the incidents page nav link, but they can navigate to it and to the upload non-CR3s page through a url. I can't remember if role-based routing permissions are implemented or relevant to VZE but wanted to check on that.

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.

oo i think thats a loophole we didnt consider and should fix, i was also able to replicate this.

question though for john -- why is the Incidents list page not allowed for read only?

@johnclary johnclary Jul 23, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We decided to keep the incident page obscured from read-only users while it's under development in order to avoid confusing users. It should be harmless if they somehow manage to reach the page, right?

Good to have a gut check on non-CR3s. Read-only users don't have update permissions on the non-cr3 table, so if they did hack the routing the page is harmless.

It's a bit sloppy that we don't redirect users away from restricted pages or show an error message. I'll open a backlog issue for that 🙏

@mddilley mddilley 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.

This looks good to ship to me! I asked a couple questions, and I can turn this into an approval real fast after that. Thanks John! Very nice to see what the shift to incidents looks like in the editor! 🙌

'exposed under a common schema for cross-type queries and geo-temporal matching.';


CREATE MATERIALIZED VIEW vz_incidents_view AS

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.

👍 and I clocked this migration at about 7 Mississippis to apply on my machine

Comment on lines +112 to +121
NULLIF(
ARRAY_AGG(DISTINCT record_incident_number ORDER BY record_incident_number)
FILTER (WHERE record_incident_number IS NOT NULL),
ARRAY[]::text[]
) as incident_numbers,
NULLIF(
ARRAY_AGG(DISTINCT record_table_name ORDER BY record_table_name)
FILTER (WHERE record_table_name IS NOT NULL),
ARRAY[]::text[]
) as record_tables,

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.

are the incident numbers and record tables expected to have corresponding values in their indices? I'm trying to think through how this data is related, and if filtering out null would offset the index in an unexpected way. I might be misunderstanding how the two columns are related!

@johnclary johnclary Jul 23, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

each row will always have a record_table, but sometimes an incident number can be null. not a concern here, but you are picking up on potential weirdness with the way to way grab address and point_feature using the sorting logic below: it's possible that either one of these are null, in which case we could potentially grab the address from one member record and the point from a different record. it's an extreme edge case but definitely a detail to iron out with the VZ team.

Comment thread editor/configs/routes.ts
path: "incidents",
label: "Incidents",
icon: FaRoad,
allowedRoles: ["editor", "vz-admin"],

@mddilley mddilley Jul 22, 2026

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.

When I was testing, I saw that the viewer cannot see the incidents page nav link, but they can navigate to it and to the upload non-CR3s page through a url. I can't remember if role-based routing permissions are implemented or relevant to VZE but wanted to check on that.

id: "other",
column: "responding_agencies_str",
operator: "_nlike",
value: "%$apd$%",

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.

nice thanks for connecting the earlier comment to this one. it was a good reminder about why we do a similar thing for team members, etc. in the Moped advanced search 🙏

Comment thread editor/configs/vzListViewTable.ts Outdated
operator: "_ilike",
wildcard: true,
},
searchFields: [{ label: "Address", value: "address" }],

@mddilley mddilley Jul 22, 2026

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.

Not related to the code changed in this PR but one thing that was a small speed bump for me when testing the search was being able to know which fields that I could search in the list view. I might have missed something or it might be really obvious to users who are familiar with the app but wanted to mention.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Similarly to Mike, when I followed the test instructions "Search by address, other columns", I tried guessing what other columns to search on (like putting in "ems") and didnt get any results. I couldnt remember if we could search on incident number of not

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sorry for that confusion: i forgot that i didn't enable searching on other columns. I added searching by Incident number as well—I think that will be valuable 🙏

Comment thread editor/app/incidents/page.tsx Outdated

@roseeichelmann roseeichelmann 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.

Its so awesome to see the incidents coming together in the UI, ive gotta be honest its been kind of hard for me to wrap my heard around incidents and incident matching, but already getting to see it laid out in this list page and map view its helping my visual brain connect the dots.

i also have a few comments/questions as well and i do think the issue mike pointed out about read only still being able to navigate to the page should be addressed if its intended for them not to see this page, but i am ready to approve asap as well!

@chiaberry chiaberry left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is looking great. I am also curious about the permissions around the incident list page. Is that temporary until the Incident details pages are implemented? I'm thinking editors+ are more informed about things in progress...

Comment thread editor/configs/vzListViewTable.ts Outdated
operator: "_ilike",
wildcard: true,
},
searchFields: [{ label: "Address", value: "address" }],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Similarly to Mike, when I followed the test instructions "Search by address, other columns", I tried guessing what other columns to search on (like putting in "ems") and didnt get any results. I couldnt remember if we could search on incident number of not

Comment thread editor/configs/vzListViewColumns.tsx Outdated
properties: GeoJsonProperties;
}

export default function VzMapPopupContent({

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I liked searching for Burnet and then toggling the map and seeing most of the incidents lining up with the street, except one or two. The future incident details page is gonna be great

'exposed under a common schema for cross-type queries and geo-temporal matching.';


CREATE MATERIALIZED VIEW vz_incidents_view AS

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

it took 4 seconds for me

- name: vz_incident
using:
foreign_key_constraint_on: vz_incident_id
- name: vz_incidents_view

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I just realized that we should name this vz_incidents_list_view to be consistent with our other record views. i'm going to fix this in later because I already have a branch going.

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.

4 participants