Back to home page

EIC code displayed by LXR

 
 

    


Warning, /swf-monitor/docs/INCLUSIVE_FILTER.md is written in an unsupported language. File is not indexed.

0001 # Inclusive filter
0002 
0003 A facet filter whose selections widen the shown set instead of narrowing
0004 it, applied in the browser. `monitor_app/inclusive_filter.py` and the
0005 include `monitor_app/_inclusive_filter.html`; first used on the campaign
0006 plan page (swf-epicprod `pcs/views.py`, `pcs_campaign_plan`).
0007 
0008 ## Behavior
0009 
0010 - Every facet lists every value with its count over all rows. Nothing
0011   disappears when a selection is made; a selected value with no rows
0012   stays listed at zero.
0013 - Clicking a value selects it, shown bold and underlined. The Match
0014   control at the top decides what the selections show. Match any, the
0015   default: the union of every selected value, across facets as well as
0016   within one, so Process DIS plus Beam 10x100 shows the rows that are
0017   DIS or at 10x100. Match all: only the rows carrying every selected
0018   value, so the same two show the DIS rows at 10x100. The control
0019   states both in one line, in the page's own noun (the plan says
0020   "configurations"; `InclusiveFilter(..., noun=...)`, default "rows").
0021 - Clicking a selected value deselects it. A facet's All clears that
0022   facet. Clear all clears everything. With nothing selected every row
0023   is shown.
0024 - The statement under the facet rows names the selections in click
0025   order: "Showing rows matching any of: Process: DIS Beam: 10x100"
0026   ("all of" under Match all).
0027 - A click costs no request. The server renders every row once with its
0028   facet values and the initial hidden state; the include's script
0029   applies each click to the page: the bold marks, the rows, the shown
0030   count, the statement, the URL. On the campaign plan, 1070 rows, the
0031   table changes in about 50 ms.
0032 
0033 ## URL contract
0034 
0035 One parameter carries the state: `f`, the selections as `facet:value`
0036 pairs joined by `|`, in click order, rewritten with `replaceState` on
0037 every click, so the address bar always holds the current state and a
0038 bookmark opens it. Back leaves the page.
0039 
0040     ?f=process:DIS|beam:10x100
0041 
0042 Match all adds `match=all`; Match any, the default, is the parameter's
0043 absence.
0044 
0045 A link may frame the page with `n`, the same syntax: pairs every row
0046 must carry, an intersection, which is what a count on another page
0047 means (the completion panel's "priority 1, below target" cell opens
0048 exactly the rows it counts). Clicks widen within the frame; clear all
0049 drops it. The statement reads "Showing rows within Priority: 1 Status:
0050 below target matching any of: ...".
0051 
0052 A page that previously carried one parameter per facet lists them in
0053 `legacy`. Those parameters meant an intersection, so they are read into
0054 the frame and dropped from the URLs the filter writes; old links open
0055 exactly what they opened before. Consumers that carry a page's filter
0056 state forward (the campaign plan's Time history embed and the Snapper
0057 Campaign focus view) carry `n`, `f` and `match`. The search, `q`,
0058 narrows the table in the browser only: no consumer carries it and the
0059 server-side apply does not read it. A value containing `|`
0060 cannot be encoded; no filtered value on the plan carries one.
0061 
0062 ## Usage
0063 
0064     from monitor_app.inclusive_filter import Facet, InclusiveFilter
0065 
0066     facets = [
0067         Facet('process', 'Process', lambda r: r['process']),
0068         Facet('requestor', 'Requestor',
0069               lambda r: r['requestors'] or ['Unassigned']),
0070         Facet('status', 'Status', lambda r: slug_of.get(r['status']),
0071               display=lambda v: label_of.get(v, v), order=[...slugs...]),
0072     ]
0073     flt = InclusiveFilter(request.GET, legacy={'process': 'process'})
0074     shown = flt.annotate(rows_all, facets)     # row['if_attr'], row['if_hidden']
0075     context['inclusive_filter'] = flt.context(rows_all, facets, request)
0076 
0077 In the template, the include above the table and two things on every
0078 row:
0079 
0080     {% include 'monitor_app/_inclusive_filter.html' %}
0081     ...
0082     <tr data-if="{{ r.if_attr }}"{% if r.if_hidden %} class="swf-if-hidden"{% endif %}>
0083 
0084 An element with class `swf-if-shown` receives the shown count. Page
0085 scripts that must follow the selection listen for the `swf-if-change`
0086 event on `document`, whose `detail` carries `selections`, `query` (the
0087 `f=...` fragment, URL-encoded) and `shown`, and read the visible rows as
0088 `tr[data-if]:not(.swf-if-hidden)`. A page with its own row selection
0089 (tick boxes, select all) restricts it to visible rows the same way.
0090 
0091 Rows live in the house sortable tables (DataTables through
0092 `swf-sortable`), which move row nodes on a sort and never recreate them,
0093 so the hidden class survives sorting; the include restripes the visible
0094 rows after each change and after each sort.
0095 
0096 `Facet(key, label, values, display=None, order=None)`: `values(row)`
0097 returns the value or values a row carries (several for a
0098 multi-membership axis; None or empty for none); `display` maps a stored
0099 value to its shown form; `order` is a list of values or a sort key,
0100 else values sort. `InclusiveFilter.echo` is the query fragment that
0101 reproduces the slice, for `urlencode`; `matches`, `apply`,
0102 `active_filters`, and the link builders `toggle_url`, `clear_facet_url`
0103 and `clear_url` serve the server side and the no-script fallback: the
0104 anchors keep server URLs, so a middle click opens the right page.
0105 
0106 ## Pages
0107 
0108 | Page | Since |
0109 |---|---|
0110 | Campaign plan (`/pcs/plan/`), both the plan and the assembly view; its delivery map follows the selection through the Snapper embed's `setHidden` hook (snapper-ai docs/INTEGRATION.md ยง 4) | 2026-09-10 |
0111 
0112 Other pages switch as decided, one at a time, by replacing their facet
0113 construction with the calls above.
0114 
0115 ## The narrowing filter
0116 
0117 The other house filter, for the operations views: a selection narrows.
0118 `monitor_app/narrowing_filter.py` and the include
0119 `monitor_app/_narrowing_filter.html`; first used on the segfault catalog
0120 (`monitor_app/viewdir/pandamon.py`, `panda_segfaults`).
0121 
0122 The two filters answer different questions. The inclusive filter serves
0123 a view where the reader assembles the set to look at (the campaign
0124 plan): selections widen, every value keeps its full count. The
0125 narrowing filter serves a view where the reader asks what a slice looks
0126 like (the operations views): a selection narrows the rows to the
0127 intersection, and every bar re-counts within the selection. A facet's
0128 own bar counts within the other facets' selections, so its alternatives
0129 stay listed with the count each would give; the other bars count
0130 within the whole selection. Picking a queue on the catalog leaves the
0131 class bar showing the classes of that queue's crashes.
0132 
0133 ### URL contract
0134 
0135 One parameter per facet, its key the facet key, one value
0136 (`?class=sparse&queue=NERSC_Perlmutter_epic`); `q` a free-text search
0137 over every column. A click on the selected value clears it, as in the
0138 inclusive filter; a click on another value replaces the selection. Every bar anchor is a server URL carrying the whole
0139 selection, so the filter works without script and a middle click opens
0140 the right page. Clear all is the page's path.
0141 
0142 ### Usage
0143 
0144     from monitor_app.narrowing_filter import Facet, NarrowingFilter
0145 
0146     facets = [
0147         Facet('class', 'Class', lambda r: r['class'],
0148               display=lambda v: labels.get(v, v), order=[...values...]),
0149         Facet('queue', 'Queue', lambda r: r['queues'] or None),
0150     ]
0151     flt = NarrowingFilter(request.GET, facets)
0152     shown = flt.annotate(rows_all, facets)      # row['nf_attr'], row['nf_hidden']
0153     context['narrowing_filter'] = flt.context(rows_all, facets, request)
0154 
0155 In the template, `{% include 'monitor_app/_narrowing_filter.html' %}`
0156 above the table renders the bars in the house filter-bar markup, the
0157 search box, and the active-filters line; every row carries
0158 `data-nf="{{ r.nf_attr }}"` and the class `swf-nf-hidden` when
0159 `r.nf_hidden` (`flt.annotate(rows_all, facets)` sets both and returns
0160 the number initially shown), and an element with class `swf-nf-shown`
0161 receives the shown count. As with the inclusive filter, a click costs
0162 no request: the server renders every row once with the request's
0163 selection applied (the no-script state, and what a link opens), and
0164 the include's script hides and shows rows, recounts every bar within
0165 the selection, and rewrites the URL with `replaceState`; the `swf-nf-change`
0166 event on `document` carries the selection, the search and the shown
0167 count to page scripts. `Facet` is the same class both filters use.
0168 
0169 ### Pages
0170 
0171 | Page | Since |
0172 |---|---|
0173 | Segfault catalog (`/panda/segfaults/`) | 2026-09-11 |
0174 
0175 The ePIC queues page and the physics-configuration page carry their own
0176 inline bars, counted over the full set; they switch when named.