Skip to content

[eCAPRIS status collector] Create view to combine Moped internal notes, Moped status updates, and eCapris status updates#1607

Merged
mddilley merged 98 commits into
mainfrom
mike/22270_notes_view
Jul 3, 2025
Merged

[eCAPRIS status collector] Create view to combine Moped internal notes, Moped status updates, and eCapris status updates#1607
mddilley merged 98 commits into
mainfrom
mike/22270_notes_view

Conversation

@mddilley
Copy link
Copy Markdown
Collaborator

@mddilley mddilley commented Jun 3, 2025

Associated issues

Closes cityofaustin/atd-data-tech#22700, cityofaustin/atd-data-tech#22217

This PR creates a new view to collect all types of notes that can be displayed in the Notes tab into one single timeline. The view handles the mapping of eCapris columns that we want to show in the UI to conform to the existing schema of Moped notes and status updates. It also joins in note type and note phase data since these details are needed for the current note features.

It also updates the project_list_view to consider whether a project has an eCapris subproject ID and if syncing is enabled. This will prevent eCapris statuses from showing on projects where the PMs have opted out of eCapris statuses (toggle is off in Notes tab) without any additional business logic in the React code or AGOL ETL.

These are the places that we are concerned about seeing status updates:

  • Project notes tab: query from new view and filter by 3 note types
  • Project summary view status update: query from new view filter to 2 status update types
  • Dashboard (My Projects and Following tabs): query from new view filter to 2 status update types
  • Project list: query from updated project_list_view which handles whether to include eCapris statuses or not
  • AGOL dataset: selects from updated project_list_view which handles whether to include eCapris statuses or not

This feels like a good way to wrangle the concept of notes in a single place - I'm open to other ideas too!

Testing

URL to test:

Local

Steps to test:

  1. Start your local stack and follow this SQL:
-- Query notes from project 331 that has eCapris subproject ID 13240.027 set on it
-- and has syncing enabled
SELECT project_id, ecapris_subproject_id, project_status_update FROM project_list_view WHERE project_id = 331;

-- Now, soft-delete the latest Moped note so that we can see that the view will use
-- the latest eCapris status
UPDATE moped_proj_notes SET is_deleted = TRUE WHERE project_note_id = 11307;

-- Repeat the query to see that the view is now showing the latest status update
-- that is sourced from eCapris
SELECT project_id, ecapris_subproject_id, project_status_update FROM project_list_view WHERE project_id = 331;

-- Check the AGOL view to see the latest project status rolled up into the component
-- rows
SELECT project_id, project_component_id, project_status_update FROM component_arcgis_online_view WHERE project_id = 331;

-- Now, disable syncing so that our view will not used eCapris statuses when
-- finding the latest status update
UPDATE moped_project SET should_sync_ecapris_statuses = FALSE WHERE project_id = 331;

-- Repeat the query to see that the view will show the next newest Moped
-- status update with eCapris syncing now disabled
SELECT project_id, ecapris_subproject_id, project_status_update FROM project_list_view WHERE project_id = 331;
SELECT project_id, project_component_id, project_status_update FROM component_arcgis_online_view WHERE project_id = 331;
  1. Now, open your Hasura console, and test some queries with data needed by the frontend:
# Get all notes associated with a project or its eCapris subproject
query GetCombinedProjectNotes {
  combined_project_notes_view(where: {_or: [{ecapris_subproject_id: {_eq: "13240.027"}}, {project_id: {_eq: 331}}]}, order_by: {created_at: desc}) {
    author
    created_at
    ecapris_subproject_id
    id
    is_editable
    note_type_name
    phase_id
    project_id
    project_note
    created_by_user_id
    original_id
    note_type_slug
    phase_key
    phase_name
  }
}
# Get only Moped-type notes
query GetCombinedProjectNotes {
  combined_project_notes_view(where: {project_id: {_eq: 331}}, order_by: {created_at: desc}) {
    author
    created_at
    ecapris_subproject_id
    id
    is_editable
    note_type_name
    phase_id
    project_id
    project_note
    created_by_user_id
    original_id
    note_type_slug
    phase_key
    phase_name
  }
}
# Get the latest status from the project_list_view
query GetProjectListLatestStatus{
  project_list_view(where: {project_id: {_eq: 331}}){
    project_id
    project_status_update
  }
}

Ship list

  • Code reviewed
  • Product manager approved
  • Product manager checked DB view dependencies
  • Product manager added to QA test script if applicable
    • Mike will write new tests to cover:
      • Adding & removing eCAPRIS subproject IDs and the expected changes to sync status
      • Toggling sync on and off in the Notes tab and seeing the expected changes in notes displayed
      • Filtering between note types
      • Disabled filtering and sync switch if no eCAPRIS subproject id
      • Seeing the expected events in the activity tab

@mddilley mddilley added the WIP Work in progress label Jun 3, 2025
@netlify
Copy link
Copy Markdown

netlify Bot commented Jun 3, 2025

Deploy Preview for atd-moped-main ready!

Name Link
🔨 Latest commit d807f4d
🔍 Latest deploy log https://app.netlify.com/projects/atd-moped-main/deploys/686689d6a7856a0008a6e21a
😎 Deploy Preview https://deploy-preview-1607--atd-moped-main.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Comment on lines +1 to +10
-- Update moped_note_types to have source column
ALTER TABLE moped_note_types
ADD COLUMN source text NOT NULL DEFAULT 'moped';

-- Insert eCapris status note type that will be used in notes view
INSERT INTO moped_note_types (name, slug, source)
VALUES
('eCapris Status Update', 'ecapris_status_update', 'ecapris');

COMMENT ON COLUMN moped_note_types.source IS 'Source of the note type, e.g., Moped or eCapris applications';
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The reasoning behind adding the eCapris note type to our lookup table is that our React code renders radio buttons and filter buttons based off values in this table. The new source column helps us control when these elements render - for example, radios to choose new note type could be limited to Moped-sourced types only.

I realize that changes the concept of this table from "Moped note types" to "types of notes that can be displayed in Moped." It is unlikely that we will see other types, and I'm hoping the source column is enough to help us remember how this works in the future.

moped_phases.phase_key AS phase_key,
moped_proj_notes.is_deleted,
moped_proj_notes.phase_id,
TRUE AS is_editable,
Copy link
Copy Markdown
Collaborator Author

@mddilley mddilley Jun 3, 2025

Choose a reason for hiding this comment

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

The intent of is_editable in this view is to help us know when to edit components like the pencil and trash icons since eCapris statuses will not be editable in Moped. This would work along with the existing business logic in the React code that note authors can only edit their own notes and statuses.

Comment on lines +44 to +51
COALESCE(
(moped_users.first_name || ' ' || moped_users.last_name),
CASE
WHEN ecapris_subproject_statuses.reviewed_by_name LIKE '%,%'
THEN TRIM(SPLIT_PART(ecapris_subproject_statuses.reviewed_by_name, ',', 2)) || ' ' || TRIM(SPLIT_PART(ecapris_subproject_statuses.reviewed_by_name, ',', 1))
ELSE LOWER(ecapris_subproject_statuses.reviewed_by_email)
END
) AS author,
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I was originally going to use reviewed_by_email as a fallback to eCapris statuses that didn't match an existing Moped user, but this column is not always populated in the eCapris database.

I could not find any statuses without reviewed_by_name or statuses where the format was not Last, First but I added a check just in case we see something different in the future.

Comment on lines +15 to +16
('M' || moped_proj_notes.project_note_id) AS id,
moped_proj_notes.project_note_id AS original_id,
Copy link
Copy Markdown
Collaborator Author

@mddilley mddilley Jun 3, 2025

Choose a reason for hiding this comment

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

The prefixed ids serve as unique keys for rendering lists of notes. The original_id is needed to know which editable notes and statuses to mutate.

Comment on lines +54 to +55
NULL AS phase_name,
NULL AS phase_key,
Copy link
Copy Markdown
Collaborator Author

@mddilley mddilley Jun 3, 2025

Choose a reason for hiding this comment

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

eCapris statuses do have "phases" with their own lookup values. No stakeholders have asked for us to attempt to map them to Moped phases, and it doesn't seem like a great idea to me to put effort into this.

Either way, our React code doesn't render a status badge when these are null which is what we want to happen.

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.

Sounds good!

@mddilley mddilley removed the WIP Work in progress label Jun 3, 2025
Copy link
Copy Markdown
Member

@johnclary johnclary left a comment

Choose a reason for hiding this comment

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

Mike, I'm blocked on replicating the DB at the moment, so I'm going to submit these handful of comments in case they're helpful. I didn't see your comments until I had reviewed. This looks great! Will come back to test it soon.

-- Most recent migration: moped-database/migrations/default/1748534889272_create_notes_view/up.sql

CREATE OR REPLACE VIEW combined_project_notes AS SELECT
'M'::text || moped_proj_notes.project_note_id AS id,
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.

is the intention to render this ID in the UI? or merely have unique ID for every row in the view? my first reaction is is to suggest you use moped_ and ecapris_ as prefixes to make it self-documenting.

Copy link
Copy Markdown
Collaborator Author

@mddilley mddilley Jun 10, 2025

Choose a reason for hiding this comment

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

yes, the intent is to have a unique id that we can depend on for React keys, and I think that adding the full names is a great call. I'll update 🙏

WHEN ecapris_subproject_statuses.reviewed_by_name ~~ '%,%'::text THEN (TRIM(BOTH FROM SPLIT_PART(ecapris_subproject_statuses.reviewed_by_name, ','::text, 2)) || ' '::text) || TRIM(BOTH FROM SPLIT_PART(ecapris_subproject_statuses.reviewed_by_name, ','::text, 1))
ELSE LOWER(ecapris_subproject_statuses.reviewed_by_email)
END
) AS author,
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.

If it's possible to include comments in these files it'd be nice to have some sample strings of what you're parsing here.

Copy link
Copy Markdown
Collaborator Author

@mddilley mddilley Jun 10, 2025

Choose a reason for hiding this comment

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

I can't remember if comments persist inside of these views after the bot handles them or not so I'll give it a go, and we'll find out. 🚀

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Nope, the comments didn't make it through. Maybe an improvement that we could look into down the road with a future VZ SQL bot. 🤖

moped_note_types.slug AS note_type_slug,
null::text AS phase_name,
null::text AS phase_key,
false AS is_deleted,
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 can't recall if you mentioned whether or not ecapris statuses may ever be deleted upstream?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not sure if I mentioned or not but they are not deleted upstream. We should be covered handling edits only.

null::text AS phase_key,
false AS is_deleted,
null::integer AS phase_id,
false AS is_editable,
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.

Nice! this view is looking great—I can see how this is going to feed nicely into the UI code 🚀

Comment on lines +54 to +55
NULL AS phase_name,
NULL AS phase_key,
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.

Sounds good!

mddilley and others added 28 commits June 23, 2025 16:32
…pris_false

Update should_sync_ecapris_statuses to false for projects without eCAPRIS IDs
Handle eCAPRIS sync activity in log when there is no eCAPRIS subproject id
…ty_log

Show eCapris status syncing updates in project activity log
…atuses

Show eCapris statuses in Notes tab
@mddilley mddilley merged commit e9e2de1 into main Jul 3, 2025
5 checks passed
@mddilley mddilley deleted the mike/22270_notes_view branch July 3, 2025 14:01
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.

Create view to combine Moped internal notes, Moped status updates, and eCapris status updates

6 participants