Put ownership on the console - #170
Merged
Merged
Conversation
Part 3 of #146, on top of the API in #165. The screens: a `/ownership` administration page for teams, routing rules and response targets, the owner and due columns on the findings queue, an owner picker on the triage panel, and the two ownership tiles on the overview. Escalation notifications are the last PR. **One screen, not three.** A team with no rule owns nothing and a rule with no target produces no deadline; an operator setting this up is answering one question, not visiting three pages. **Rules are shown in the order routing evaluates them.** Order is the entire meaning of a rule set, and a screen that displayed a friendlier one would be a screen you could not use to reason about routing. Disabled rules stay in place, because their position is what somebody is thinking about when they consider turning one back on. **Disband is the primary action, delete is conditional.** A team that owns findings cannot be deleted at all — the API answers 409 — so the screen leads with disbanding and hides the delete control entirely for a team with open work, rather than offering a button whose only outcome is an error. **"Leave unchanged" has to be expressible.** A `<select>` cannot post an omitted field, and *any* owner value pins a finding against future routing. Without an empty option an analyst could not add a triage comment without also taking the routing decision away from the rules, so `owner` reads the same three ways `assignee` already does: empty is unchanged, `none` is null, anything else is an id. In the opposite direction, the queue collapses `?owner_group_id=` and `?unowned=` into one `?owner=` dropdown — the API keeps them separate because an absent parameter already means "do not filter" there, while two controls that must not both be set is a state a URL can carry and a person can get wrong. **Overdue is computed once, in Python.** `ownership.overdue` is widened to a Protocol so the console can ask it of a `FindingRead`, because the alternative is the template re-deriving it in Jinja and a second definition is the copy that drifts. The overview counts both queues by asking the API rather than filtering the open-findings page, which is capped — deriving them would under-report exactly when the backlog is large enough to matter. 23 tests in `apps/api/tests/test_web_ownership.py`, plus docs/web.md. make check green.
There was a problem hiding this comment.
Verdict
APPROVE
Completed bounded review across 1 immutable scope(s). One medium-impact navigation defect found in the new ownership screen.
Scope health
Convergence: healthy. Review mode: initial.
Recommended action: CONTINUE_INCREMENTAL.
- No escalation signals.
Prior findings
| Finding | Status |
|---|---|
| — | No prior finding state |
New findings
Root cause: The template exposes the underlying API filter name instead of the web route's normalized query parameter.
- FOLLOW-UP ISSUE · medium: Team queue links use an ignored query parameter —
apps/api/src/iceberg_api/web/templates/admin/ownership.html:70
Status: NEW. Attribution: new_in_scope.
The ownership screen generates links withowner_group_id, but the web findings route only reads theownerquery parameter and translates it to the API'sowner_group_id. Consequently, the team-specific Open and Overdue counters navigate to an unfiltered findings queue (apart from state/overdue).
Invariant: A team-specific ownership link must preserve the selected team when navigating to the findings queue.
Ownership: Web ownership template and web findings-route query adapter.. Behaviour: The Teams table's Open and Overdue links..
Evidence:ownership.htmllinks to/findings?owner_group_id={{ group.id }}...;findings.pyacceptsownerand callsid_or_none(owner), with noowner_group_idrequest parameter. The overview correctly usesowner=none, and the added tests exercise?owner={group.id}.
Fix-induced regressions
- None evidenced.
Uncertainty
- No material uncertainty recorded.
Validation
- Static review of the supplied immutable diff; exact-head CI was reported as passed in the review data.
Residual risks
- None identified.
9 tasks
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.
Part 3 of #146, on top of the API in #165. The screens. Escalation notifications
are the last PR.
What lands
/ownership(admin) — teams, routing rules, and response targets on one page.The calls worth arguing about
One screen, not three. A team with no rule owns nothing; a rule with no target
produces no deadline. An operator setting this up is answering one question — who
looks after what, and how fast — not visiting three pages.
Rules are shown in the order routing evaluates them, disabled ones included.
Order is the entire meaning of a rule set, and a screen that displayed a friendlier
one would be a screen you could not use to reason about routing. A disabled rule's
position is exactly what somebody is thinking about when they consider turning it
back on.
Disband is the primary action; delete is conditional. A team that owns findings
cannot be deleted at all — the API answers 409, because the FK is
SET NULLandthe delete would otherwise orphan a whole team's work. So the screen leads with
disbanding and hides the delete control entirely for a team with open findings,
rather than offering a button whose only outcome is an error.
"Leave unchanged" has to be expressible. A
<select>cannot post an omittedfield, and any owner value pins a finding against future routing. Without an
empty option, an analyst could not add a triage comment without also taking the
routing decision away from the rules. So
ownerreads the same three waysassigneealready does: empty is unchanged,noneis null, anything else is an id.In the opposite direction, the queue collapses
?owner_group_id=and?unowned=into a single
?owner=dropdown. The API keeps them separate because an absentquery parameter already means "do not filter" there; a dropdown has no such
ambiguity, and two controls that must not both be set is a state a URL can carry
and an operator can get wrong.
Overdue is computed once, in Python.
ownership.overdueis widened to aProtocol so the console can ask it of a
FindingRead— the alternative is thetemplate re-deriving overdue in Jinja, and a second definition is the copy that
drifts. The overview counts both queues by asking the API rather than filtering the
open-findings page, which is capped: deriving them would under-report exactly when
the backlog is large enough to matter.
Invariants
Every route resolves
WebAdmin/WebVieweritself and then calls the API handler asa function — calling a handler directly does not run its own
Depends(docs/web.md).Nothing under
iceberg_api.webtouches the ORM, no template gains an inline<script>/<style>or anonclick=, and the existing invariant tests(
test_web_invariants.py,test_web_shell.py) pass unchanged.The owner picker is offered to analysts even though the user picker is admin-only:
listing teams is viewer+ at the API, because a team is not a directory of people.
Acceptance criteria, against #146
option, an overview tile, and a link from every team's row
and the queue filters on owner, unowned, and overdue
counts on the ownership screen
23 tests in
apps/api/tests/test_web_ownership.py;make checkgreen.