No production file changes. The page's output is unchanged because nothing the page runs was edited; the commit adds tests and the builders they use. TestOrgEnrollmentGolden compares, for nine states (no organization, no pool, the default plan alone, a rich organization with one plan on two ladders and a subscription on a third, a grant with a note, a flagged subscription, usage, an open overdue invoice and a rule change, a removed tier, a second pool, a suspended subscription, twelve tier changes paged, and the rich one searched), the data the loader returns and the operator_enrollment.html render with files under testdata/org_enrollment. The data leaves out the Issue grant form, the grants ledger's form and the grants rows, which the render holds. The files were read against the state table before they were committed. TestOrgEnrollmentReadFailures fails each of 23 reads in turn and pins the section it feeds and, where it is words on the page, the render. The operator read-count test gains the row "organization page" at 9 reads per row, as predicted. world gains Member, Suspend, Pool and ConferOn, and each says which state it writes that the console does not: Pool and ConferOn, a second pool; Member, a member who is not an organization's owner; Suspend, a suspended subscription whose row keeps its status. Shown to change no output: 66 breaks of the page's code, each applied on its own, are all caught by the new tests; the tests of ./internal/server and ./internal/testkit/... pass, with no test or subtest of the parent missing or changed in result, and 35 added in ./internal/server (the golden's 10, the failure table's 24 and the read-count row); the golden passes three runs in a row on a reset database. Unprobed, and why: - The two advisory failure rows keep their fallback in the nothing-held state; no one-line edit flags a product without the rows the failed read did not return, so a break there cannot be written. - The order of the rungs of one act (Hosting before Chat) is the position query's order among equal keys, which it does not specify; accepted. - The members cap: no state holds more than 16 members. - Values copied unchanged from a row (organization, pool, set and resource names, keys, statuses, emails, roles, notes, limit lines): a copy can only be dropped, and the golden shows a drop. - The ledger's rows, its History tab and its search beyond one break: the grants goldens pin them; this page's render only holds them. - The pager's own markup (the Showing line, Previous and Next): the pager test of the embedded lists and the list-scale tests pin it. - The pool type badge, which is a copied value the template renders.
433 lines
18 KiB
HTML
433 lines
18 KiB
HTML
|
||
|
||
<div class="d-flex align-items-start justify-content-between gap-3 mb-3 app-page-header">
|
||
<div class="app-page-header-text">
|
||
<nav aria-label="breadcrumb"><ol class="breadcrumb small mb-1">
|
||
<li class="breadcrumb-item"><a href="/operator">Operator</a></li><li class="breadcrumb-item"><a href="/operator/organizations">Organizations</a></li>
|
||
<li class="breadcrumb-item active" aria-current="page">many-changes</li>
|
||
</ol></nav>
|
||
<h1 class="h2 mb-0">many-changes</h1>
|
||
|
||
</div>
|
||
|
||
|
||
</div>
|
||
|
||
|
||
<div class="card mb-3">
|
||
<div class="card-body">
|
||
<dl class="row mb-0">
|
||
<dt class="col-sm-3">Owner</dt>
|
||
<dd class="col-sm-9">
|
||
|
||
<a href="/operator/persons/<person:Ann>">Ann</a>
|
||
|
||
</dd>
|
||
|
||
|
||
</dl>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="d-flex align-items-center justify-content-between gap-3 mt-4 mb-2 app-section-header">
|
||
<h2 class="h5 mb-0">Billing</h2>
|
||
|
||
|
||
</div>
|
||
<div class="card mb-3">
|
||
<div class="card-body">
|
||
|
||
|
||
|
||
<div class="text-center py-4">
|
||
<p class="text-muted mb-2">No billing account exists for this organization yet.</p>
|
||
|
||
|
||
</div>
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="d-flex align-items-center justify-content-between gap-3 mt-4 mb-2 app-section-header">
|
||
<h2 class="h5 mb-0">Pools <button type="button" class="btn btn-link p-0 border-0 align-baseline app-help-icon" data-bs-toggle="popover" data-bs-trigger="click" data-bs-title="Pools" data-bs-content="A pool holds what an organization's plan and grants deliver; its sites and services draw from it." aria-label="Help: Pools"><svg class="form-help-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></button></h2>
|
||
|
||
|
||
</div>
|
||
|
||
|
||
<div class="card mb-3">
|
||
<div class="card-body">
|
||
<div class="d-flex justify-content-between align-items-start">
|
||
<div>
|
||
<h3 class="h6 mb-1">
|
||
Default <span class="badge text-bg-success">Active</span>
|
||
</h3>
|
||
|
||
|
||
<p class="text-muted mb-0">No plan is delivering into this pool</p>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<div class="d-flex align-items-center justify-content-between gap-3 mt-4 mb-2 app-section-header">
|
||
<h2 class="h5 mb-0">Grants</h2>
|
||
|
||
|
||
</div>
|
||
|
||
<div id="plan-grants-panel">
|
||
<div class="card mb-3">
|
||
<div class="card-header d-flex justify-content-between align-items-center gap-2">
|
||
|
||
<ul class="nav nav-tabs card-header-tabs">
|
||
|
||
<li class="nav-item"><a class="nav-link active" href="/operator/organizations/<org:many-changes>?tc_page=2" hx-get="/operator/organizations/<org:many-changes>?tc_page=2" hx-target="#plan-grants-panel" hx-select="#plan-grants-panel" hx-select-oob="#tier-changes-panel,#entitlement-changes-panel" hx-swap="outerHTML" hx-push-url="true">Active</a></li>
|
||
|
||
<li class="nav-item"><a class="nav-link " href="/operator/organizations/<org:many-changes>?tab=history&tc_page=2" hx-get="/operator/organizations/<org:many-changes>?tab=history&tc_page=2" hx-target="#plan-grants-panel" hx-select="#plan-grants-panel" hx-select-oob="#tier-changes-panel,#entitlement-changes-panel" hx-swap="outerHTML" hx-push-url="true">History</a></li>
|
||
|
||
</ul>
|
||
|
||
<div class="flex-shrink-0">
|
||
|
||
|
||
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-toggle="collapse" data-bs-target="#issueGrantPanel" aria-expanded="false" aria-controls="issueGrantPanel"><svg class="app-opener-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" aria-hidden="true" focusable="false"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>Issue grant</button>
|
||
|
||
</div>
|
||
</div>
|
||
<div class="card-body">
|
||
|
||
|
||
<div class="collapse" id="issueGrantPanel">
|
||
|
||
|
||
<div class="card app-form-box mb-3"><div class="card-body">
|
||
<form id="form-operator.enrollment.grant.issue" data-form="operator.enrollment.grant.issue" data-form-kind="create" class="app-form" novalidate hx-post="/partials/operator/organizations/<org:many-changes>/grant/create" hx-target="#operator-body" hx-swap="innerHTML" hx-disable="find button[type=submit]" hx-indicator="find .htmx-indicator">
|
||
<div id="form-operator.enrollment.grant.issue-error" class="app-form-error" role="alert"></div>
|
||
|
||
<div class="app-form-message mb-2">A tier product moves the pool to that tier, up or down.</div>
|
||
|
||
|
||
|
||
<div class="app-form-fields">
|
||
<div id="form-operator.enrollment.grant.issue-product_id" class="mb-3 app-form-field">
|
||
|
||
|
||
<label class="form-label" for="form-operator.enrollment.grant.issue-product_id-control">Product</label>
|
||
|
||
<select class="form-select" id="form-operator.enrollment.grant.issue-product_id-control" name="product_id" required aria-describedby="form-operator.enrollment.grant.issue-product_id-warning" hx-get="/partials/operator/products/rule-check" hx-swap="innerHTML" hx-target="#form-operator\.enrollment\.grant\.issue-product_id-warning" hx-trigger="change">
|
||
<option value="" disabled selected>Choose a product</option>
|
||
<option value="<product:Basic>">Basic</option>
|
||
<option value="<product:Forum>">Forum</option>
|
||
<option value="<product:Forum2>">Forum2</option>
|
||
<option value="<product:Large>">Large</option>
|
||
<option value="<product:Lite>">Lite</option>
|
||
<option value="<product:Lite2>">Lite2</option>
|
||
<option value="<product:Mid>">Mid</option>
|
||
<option value="<product:Plus>">Plus</option>
|
||
<option value="<product:Pro>">Pro</option>
|
||
<option value="<product:Small>">Small</option>
|
||
<option value="<product:Small2>">Small2</option>
|
||
<option value="<product:Starter>">Starter</option>
|
||
<option value="<product:Starter2>">Starter2</option>
|
||
<option value="<product:Top>">Top</option>
|
||
|
||
</select>
|
||
|
||
<div id="form-operator.enrollment.grant.issue-product_id-warning"></div>
|
||
|
||
|
||
|
||
|
||
</div><div id="form-operator.enrollment.grant.issue-quantity" class="mb-3 app-form-field">
|
||
|
||
|
||
<label class="form-label" for="form-operator.enrollment.grant.issue-quantity-control">Quantity <span class="text-muted">(optional)</span></label> <button type="button" class="btn btn-link p-0 border-0 align-baseline app-help-icon" data-bs-toggle="popover" data-bs-trigger="click" data-bs-title="Quantity" data-bs-content="Seats, for per-seat products." aria-label="Help: Quantity"><svg class="form-help-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></button>
|
||
|
||
<input type="number" class="form-control" id="form-operator.enrollment.grant.issue-quantity-control" name="quantity" value="" placeholder="1" min="1" max="1000000">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
</div><div id="form-operator.enrollment.grant.issue-valid_until" class="mb-3 app-form-field">
|
||
|
||
|
||
<label class="form-label" for="form-operator.enrollment.grant.issue-valid_until-control">Valid until <span class="text-muted">(optional)</span></label> <button type="button" class="btn btn-link p-0 border-0 align-baseline app-help-icon" data-bs-toggle="popover" data-bs-trigger="click" data-bs-title="Valid until" data-bs-content="Leave blank for a grant with no end date." aria-label="Help: Valid until"><svg class="form-help-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></button>
|
||
|
||
<input type="datetime-local" class="form-control" id="form-operator.enrollment.grant.issue-valid_until-control" name="valid_until" value="" >
|
||
|
||
|
||
|
||
|
||
|
||
|
||
</div><div id="form-operator.enrollment.grant.issue-reason" class="mb-3 app-form-field">
|
||
|
||
|
||
<label class="form-label" for="form-operator.enrollment.grant.issue-reason-control">Reason</label>
|
||
|
||
<select class="form-select" id="form-operator.enrollment.grant.issue-reason-control" name="reason" required>
|
||
<option value="" disabled selected>Choose a reason</option>
|
||
<option value="manual">manual</option>
|
||
<option value="evaluation">evaluation</option>
|
||
<option value="promotional">promotional</option>
|
||
<option value="complimentary">complimentary</option>
|
||
<option value="sponsored">sponsored</option>
|
||
<option value="board_decision">board_decision</option>
|
||
<option value="legacy">legacy</option>
|
||
|
||
</select>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
</div><div id="form-operator.enrollment.grant.issue-description" class="mb-3 app-form-field">
|
||
|
||
|
||
<label class="form-label" for="form-operator.enrollment.grant.issue-description-control">Note <span class="text-muted">(optional)</span></label> <button type="button" class="btn btn-link p-0 border-0 align-baseline app-help-icon" data-bs-toggle="popover" data-bs-trigger="click" data-bs-title="Note" data-bs-content="Why this grant is issued. Shown with the grant in its history." aria-label="Help: Note"><svg class="form-help-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></button>
|
||
|
||
<input type="text" class="form-control" id="form-operator.enrollment.grant.issue-description-control" name="description" value="" maxlength="500" autocomplete="off">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
</div><div id="form-operator.enrollment.grant.issue-resumes_after_supersession" class="mb-3 app-form-field">
|
||
|
||
|
||
|
||
<label class="form-label" for="form-operator.enrollment.grant.issue-resumes_after_supersession-control">Resumes</label> <button type="button" class="btn btn-link p-0 border-0 align-baseline app-help-icon" data-bs-toggle="popover" data-bs-trigger="click" data-bs-title="Resumes" data-bs-content="If a plan replaces this grant, the grant delivers again when that plan ends." aria-label="Help: Resumes"><svg class="form-help-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></button>
|
||
|
||
<div class="app-form-check-control"><input class="form-check-input" type="checkbox" id="form-operator.enrollment.grant.issue-resumes_after_supersession-control" name="resumes_after_supersession" value="true" ></div>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
<div class="d-flex flex-wrap gap-2 app-form-commit">
|
||
|
||
<button type="submit" class="btn btn-primary"><span class="spinner-border spinner-border-sm htmx-indicator me-1" aria-hidden="true"></span>Issue grant</button>
|
||
|
||
|
||
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="collapse" data-bs-target="#issueGrantPanel">Cancel</button>
|
||
|
||
|
||
</div>
|
||
|
||
</form>
|
||
</div></div>
|
||
|
||
|
||
</div>
|
||
|
||
|
||
<div class="d-flex flex-wrap align-items-center gap-2 mb-2">
|
||
|
||
|
||
|
||
|
||
|
||
<form id="form-operator.list.controls" data-form="operator.list.controls" data-form-kind="search" class="app-form app-form-bar" method="GET" action="/operator/organizations/<org:many-changes>" role="search" hx-get="/operator/organizations/<org:many-changes>" hx-target="#plan-grants-panel" hx-swap="outerHTML" hx-push-url="true" hx-select="#plan-grants-panel" hx-select-oob="#tier-changes-panel,#entitlement-changes-panel">
|
||
<div id="form-operator.list.controls-error" class="app-form-error" role="alert"></div>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<label class="form-label visually-hidden" for="form-operator.list.controls-q-control">Search this list</label>
|
||
<input type="hidden" name="tc_page" value="2" >
|
||
<div class="input-group">
|
||
<span class="input-group-text"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true"><path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.44.44.98.98l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.98-.98zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"/></svg></span>
|
||
<input type="text" class="form-control" id="form-operator.list.controls-q-control" name="q" value="" placeholder="Product or reason" autocomplete="off">
|
||
|
||
|
||
<button type="submit" class="btn btn-outline-secondary"><span class="spinner-border spinner-border-sm htmx-indicator me-1" aria-hidden="true"></span>Search</button>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</form>
|
||
|
||
|
||
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<div class="text-center py-4">
|
||
<p class="text-muted mb-2">No grants are delivering right now.</p>
|
||
<p class="text-muted small mb-2">The History tab lists every grant ever issued.</p>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="d-flex align-items-center justify-content-between gap-3 mt-4 mb-2 app-section-header">
|
||
<h2 class="h5 mb-0">Members</h2>
|
||
|
||
|
||
</div>
|
||
<div class="card mb-3">
|
||
<div class="card-body">
|
||
|
||
|
||
|
||
|
||
<div class="text-center py-4">
|
||
<p class="text-muted mb-2">No members.</p>
|
||
|
||
|
||
</div>
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
|
||
|
||
<div class="d-flex align-items-center justify-content-between gap-3 mt-4 mb-2 app-section-header">
|
||
<h2 class="h5 mb-0">Entitlement changes</h2>
|
||
|
||
|
||
</div>
|
||
|
||
<div id="entitlement-changes-panel">
|
||
<div class="card mb-3">
|
||
<div class="card-body">
|
||
|
||
|
||
|
||
|
||
<div class="text-center py-4">
|
||
<p class="text-muted mb-2">No entitlement changes recorded yet.</p>
|
||
|
||
|
||
</div>
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
|
||
<div class="d-flex align-items-center justify-content-between gap-3 mt-4 mb-2 app-section-header">
|
||
<h2 class="h5 mb-0">Tier changes <button type="button" class="btn btn-link p-0 border-0 align-baseline app-help-icon" data-bs-toggle="popover" data-bs-trigger="click" data-bs-title="Tier changes" data-bs-content="Every tier change on this organization's plan ladders, newest first. Tier names reflect the ladder's current shape." aria-label="Help: Tier changes"><svg class="form-help-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></button></h2>
|
||
|
||
|
||
</div>
|
||
|
||
<div id="tier-changes-panel">
|
||
<div class="card mb-3">
|
||
<div class="card-body">
|
||
|
||
|
||
<div class="table-responsive">
|
||
<table class="table table-hover table-sm table-record">
|
||
<thead>
|
||
<tr>
|
||
<th>Ladder</th>
|
||
<th>Change</th>
|
||
<th>From</th>
|
||
<th>To</th>
|
||
<th>By</th>
|
||
<th>Reason</th>
|
||
<th>When</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
|
||
<tr>
|
||
<th scope="row">Cycle</th>
|
||
<td><span class="badge text-bg-light border">Ended</span></td>
|
||
<td>Lite2</td>
|
||
<td>—</td>
|
||
<td>
|
||
Ann
|
||
</td>
|
||
<td class="text-break" title="operator_revocation">Revoked by an operator</td>
|
||
<td class="text-nowrap"><small><span class="d-block"><today></span><span class="d-block text-muted"><hh:mm></span></small></td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<th scope="row">Cycle</th>
|
||
<td><span class="badge text-bg-light border">Started</span></td>
|
||
<td>—</td>
|
||
<td>Lite2</td>
|
||
<td>
|
||
Ann
|
||
</td>
|
||
<td class="text-break" title="test fixture">Complimentary</td>
|
||
<td class="text-nowrap"><small><span class="d-block"><today></span><span class="d-block text-muted"><hh:mm></span></small></td>
|
||
</tr>
|
||
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
|
||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mt-2">
|
||
<p class="small text-body-secondary mb-0">Showing 11–12 of 12</p>
|
||
|
||
<p class="small text-body-secondary mb-0">Per page:
|
||
|
||
<strong>10</strong>
|
||
|
||
<a href="/operator/organizations/<org:many-changes>?tc_per=25" hx-get="/operator/organizations/<org:many-changes>?tc_per=25" hx-target="#tier-changes-panel" hx-select="#tier-changes-panel" hx-select-oob="#plan-grants-panel,#entitlement-changes-panel" hx-swap="outerHTML" hx-push-url="true">25</a>
|
||
|
||
<a href="/operator/organizations/<org:many-changes>?tc_per=50" hx-get="/operator/organizations/<org:many-changes>?tc_per=50" hx-target="#tier-changes-panel" hx-select="#tier-changes-panel" hx-select-oob="#plan-grants-panel,#entitlement-changes-panel" hx-swap="outerHTML" hx-push-url="true">50</a>
|
||
|
||
</p>
|
||
|
||
|
||
<nav aria-label="List pages">
|
||
<ul class="pagination pagination-sm mb-0">
|
||
<li class="page-item "><a class="page-link" href="/operator/organizations/<org:many-changes>" hx-get="/operator/organizations/<org:many-changes>" hx-target="#tier-changes-panel" hx-select="#tier-changes-panel" hx-select-oob="#plan-grants-panel,#entitlement-changes-panel" hx-swap="outerHTML" hx-push-url="true">Previous</a></li>
|
||
<li class="page-item disabled"><span class="page-link">Page 2 of 2</span></li>
|
||
<li class="page-item disabled"><a class="page-link" href="/operator/organizations/<org:many-changes>?tc_page=3" aria-disabled="true" tabindex="-1">Next</a></li>
|
||
</ul>
|
||
</nav>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
|
||
|