Skip to content

Agency Management admin screen (Phase 4) - #192

Open
collinschreyer-dev wants to merge 7 commits into
devfrom
feature/agency-management-ui
Open

Agency Management admin screen (Phase 4)#192
collinschreyer-dev wants to merge 7 commits into
devfrom
feature/agency-management-ui

Conversation

@collinschreyer-dev

Copy link
Copy Markdown
Collaborator

Adds an AGENCIES tab to the admin console. Pairs with the ten admin endpoints in GSA/srt-api#207merge that first, or the screen has nothing to call.

Everything here previously required a developer to edit AGENCY_LOOKUP or UNIQUE_EMAIL_AGENCY_MAPPING in config.js and 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:

  • adjacent columns, Sees solicitations from and Deviation applied
  • separate editors saving through separate endpoints, so neither edit can move the other
  • the access editor states it; 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/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:

old algorithm rendered: ["Department of Defense","Department of the Navy"]
NAVSEA present? NO — row silently dropped

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.ts covers all six cases, with the three-level case as an explicit regression test.

Verification

  • Production build clean; only pre-existing SASS and quill warnings.
  • tsc --noEmit clean for the new files (repo-wide spec errors from missing jasmine types are pre-existing).
  • Ordering algorithm exercised against fixtures — 6/6, including the case that broke the first version.
  • Not exercised against a live API: no SRT database is reachable locally, so the screen has not been driven end to end. Worth a pass on staging once srt-api#207 lands.

🤖 Generated with Claude Code

collinschreyer-dev and others added 7 commits August 31, 2026 12:16
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>
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>
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.

1 participant