Agency Management admin screen (Phase 4) - #192
Open
collinschreyer-dev wants to merge 7 commits into
Open
Conversation
Companion to the srt-api change that adds token() and admin_only() to the rag-analytics routes. Without this, those calls would start returning 401. Angular attaches the bearer token through TokenInterceptor, which only sees HttpClient traffic. Sixteen calls in the analytics and home components used raw fetch() instead, which bypasses the interceptor and sent no Authorization header. That is why those endpoints had no server-side guard: adding one would have broken them. art-lookup, the single guarded route, was also the single one called through HttpClient. Adds authHeaders() in shared/services and applies it at each fetch call site. home.component already did this by hand for its streaming analyze call, so this generalizes an existing local fix rather than introducing a new pattern. The execute-pipeline upload deliberately gets no Content-Type, so the browser can set the multipart boundary for its FormData body. No request logic changed beyond the added header. Verified with a production build and a typecheck of the touched files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds an AGENCIES tab to the admin console for managing the agency hierarchy, email domain mappings, solicitation access, and deviation inheritance. Pairs with the ten admin endpoints in srt-api. Everything on this screen previously required a developer to edit AGENCY_LOOKUP or UNIQUE_EMAIL_AGENCY_MAPPING in config.js and redeploy. The screen is organised around the distinction the whole change exists to establish: which solicitations an agency's users can SEE is a different relationship from whose deviation APPLIES to them. They occupy adjacent columns, have separate editors, and save through separate endpoints, so neither edit can quietly move the other. The access editor says so, and the parent editor warns that reparenting changes inherited deviation but not visibility. Two views: Agencies and domains — the hierarchy as an indented table with domains, user counts, access, and deviation per row. Inline editors for details and parent, access as a multi-select, deviation as a single select defaulting to inherit, and domain add and remove. Search matches name, acronym, parent, or domain. Agencies deactivate rather than delete, and deactivating one with users attached says how many are affected first. Needs review — unresolved login domains grouped by domain with a waiting-user count, carried as a badge on the tab. Resolving assigns every user on that domain at once and maps it for future sign-ups. A domain can be attached to an existing agency or to a new component under a named parent; creating a top-level agency is deliberately not possible from this queue, matching the server-side guardrail. Includes a regression test. The first implementation grouped components one level under their parent, which silently dropped every third-level component (NAVSEA under Navy under DOD) from the table with no error. Replaced with a depth-first walk that handles arbitrary nesting, pulls in the full ancestor chain when a descendant matches a search, surfaces components whose parent is missing instead of hiding them, and terminates on a cycle rather than hanging the render. Verified with a production build and by exercising the ordering algorithm against fixtures for all six cases. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Email templates are now loaded from the API instead of a hardcoded array in the component. An administrator can edit and save a template, create their own, and remove one, rather than editing text that lasted for a single send and then reverted. Saving is deliberately separate from sending, and the screen says so. Editing the subject or body still affects only the current send unless the change is saved back to the template, which preserves how the screen already worked for anyone used to it. Two things the move to stored templates would otherwise have broken silently. The template id changed from a string key to a numeric row id, and the template was carrying conditional editors keyed on that id, so the update-notes and inactivity fields would have stopped appearing with no error. Those now compare templateKey. The bulk send also passes templateKey rather than the row id, because admin_audit_log rows already reference the key from when the templates were hardcoded, and sending the numeric id would have split the send history. Built-in templates offer Hide rather than Delete, matching the API, which deactivates them so the set SRT ships with can always be restored. The agency table now shows the alternate spellings an agency answers to. A merged duplicate lives on as an alias rather than a row, so this is what explains where an agency went and why its solicitations appear under more than one name. Verified with a production build and a typecheck of the touched files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…egration/auth-and-agency
The admin container was capped at the USWDS default of 1200px, which is a prose width. The Users and Agencies tables have seven columns and were being cut off on the right, so a single row could not be read without scrolling. Raised to 1600px, and to 1850px on a genuinely wide display. Search fields on both tabs now draw their own border. input[type="search"] inherits a browser appearance that leaves the right edge open in WebKit, so the field read as unfinished rather than as a box. Table cells no longer stack. Every value in those columns is a short phrase that reads as a unit, and letting them wrap turned "2 domains" and the access and deviation values into two or three stacked lines that were hard to scan. Each column now has enough width to hold its label on one line. The agency name is the only column still allowed to wrap, because agency names are genuinely long. Renamed the default access value from "Own solicitations only" to "Own agency only". Read together with the column heading the old wording produced "Sees solicitations from own solicitations only", which parses as nonsense and was the source of the confusion it caused. Added a short note above the table explaining the two columns whose meaning is not self-evident, and why they are separate: a component can follow its parent's deviation without being able to see the parent's solicitations. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…roll The agency table listed all 653 agencies at once. The hierarchy only helps if you can scan the departments first and open the one you want, so departments now start collapsed and open like a drawer. Each closed department shows how many components are hiding under it, and there are Expand all and Collapse all controls. A search bypasses collapsing entirely. Hiding a row the user just searched for behind a closed parent would be worse than having no hierarchy at all, so while a search is running every match stays visible with its ancestors, and the disclosure arrows are withdrawn rather than left in a state that does nothing. The Users table could not be scrolled to the right, so Created and Updated were unreachable. The wrapper already had overflow-x: auto, but the table carried width-full, which pinned it to the container width. Nothing ever exceeded the box, so no scrollbar appeared and the right-hand columns were squeezed out instead of overflowing. Removing width-full and giving the table a min-width lets it be genuinely wider than its container, which is what makes the scrollbar appear. Verified the ordering against fixtures: collapsed shows only departments, opening one reveals its own components and does not cascade to grandchildren, and a search returns the match with its full ancestor chain regardless of what is open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nder Adds a per-column filter row matching the one the Users tab already has, so the two admin tables behave the same way. Name, type, domain, solicitation access and deviation each filter independently and are ANDed together, with a Clear control that appears once anything is set. Any active filter suspends collapsing, for the same reason a search does. A filter whose matches were hidden inside closed departments would look like it had returned nothing at all. The disclosure arrow alone was a poor target and did not read as interactive. On a row that has components the whole agency name is now the control, styled as a link, carrying the component count inside it. The arrow stays as a secondary affordance. Rows with nothing to open render as plain text, so the styling distinguishes what can be expanded from what cannot. Verified against fixtures: filtering by name reaches a component buried under a collapsed department, domain and type filters work, filters combine, and a filter matching nothing returns nothing rather than falling back to everything. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds an AGENCIES tab to the admin console. Pairs with the ten admin endpoints in GSA/srt-api#207 — merge that first, or the screen has nothing to call.
Everything here previously required a developer to edit
AGENCY_LOOKUPorUNIQUE_EMAIL_AGENCY_MAPPINGinconfig.jsand redeploy.The organising idea
Which solicitations an agency's users can see is a different relationship from whose deviation applies to them. A component can inherit its parent's deviation without seeing the parent's solicitations.
The screen keeps that visible rather than assuming anyone remembers it:
Sees solicitations fromandDeviation appliedTwo views
Agencies and domains — the hierarchy as an indented table with domains, user counts, access, and deviation per row. Inline editors for details/parent, access as a multi-select, deviation as a single select defaulting to inherit, and domain add/remove. Search matches name, acronym, parent, or domain. Agencies deactivate rather than delete; deactivating one with users attached says how many are affected first.
Needs review — unresolved login domains grouped by domain with a waiting-user count carried as a badge on the tab. Resolving assigns every user on that domain at once and maps it for future sign-ups. A domain can go to an existing agency or a new component under a named parent — creating a top-level agency is deliberately not possible here, matching the server guardrail.
A bug caught in review
My first implementation grouped components one level under their parent. That silently dropped every third-level component from the table — NAVSEA under Navy under DOD simply did not render, with no error anywhere:
Replaced with a depth-first walk that handles arbitrary nesting, pulls in the full ancestor chain when a descendant matches a search, surfaces components whose parent is missing instead of hiding them, and terminates on a cycle rather than hanging the render.
agency-management.component.spec.tscovers all six cases, with the three-level case as an explicit regression test.Verification
tsc --noEmitclean for the new files (repo-wide spec errors from missing jasmine types are pre-existing).🤖 Generated with Claude Code