Add negation and JS-expression search in AE listing detail table - #146
Open
yihui wants to merge 7 commits into
Open
Add negation and JS-expression search in AE listing detail table#146yihui wants to merge 7 commits into
yihui wants to merge 7 commits into
Conversation
Users can now prefix search terms with "!" to exclude matching rows (e.g., "!group A" filters out rows containing "group A"). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Users can now type arbitrary JS expressions in the search bar using `x`
as the cell value, e.g. `x != 'Placebo'`, `x > 80`, `x.includes('foo')`.
Negation expressions filter with every() (exclude matching), positive
expressions filter with some() (include matching). Plain text still works
as substring search.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Restore the every()/some() split for JS-expression searches so that
negation expressions filter correctly:
- A negation expression (e.g. `x !== "Rash"`, `!x.includes("Rash")`) now
keeps a row only when EVERY cell satisfies the test, so rows whose value
matches the excluded term are dropped. Previously it used some(), so any
non-matching cell (e.g. age) kept the whole row and nothing was filtered.
- Positive expressions still keep a row when ANY cell satisfies the test.
Also keeps the substring path with a leading `!` for simple negation
(e.g. `!Rash`), which is disambiguated from expression mode by whether the
term references the cell variable `x`.
Verified in a headless browser (chromote) against a reactable with the
exact searchMethod: `!Rash`, `Rash`, `x !== "Rash"`, `x > 50`, and
`!x.includes("Rash")` all filter as expected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 tasks
The previous change only customized the table-wide `searchMethod` (the
global search box), but the AE listing detail table exposes a filter box
per column (`filterable = TRUE`). Those per-column boxes use the default
substring `filterMethod`, so `!Rash` or `x !== "Rash"` matched nothing
and returned an empty table.
Add a custom `filterMethod` to every column definition mirroring the
global search behavior for a single column:
- Substring match, with a leading `!` to negate (e.g. `!Rash`).
- JS expression evaluation when the term references the cell value `x`
(e.g. `x > 5`, `x !== "Rash"`, `!x.includes("Rash")`), tried against
both the string and, when applicable, numeric form of the cell.
Verified in a headless browser (chromote) against the real ae_forestly()
widget: expanding a detail row and typing into the Adverse Event and
Gender column filters, `F`/`!F`, `x === "F"`/`x !== "F"`, and
`!x.includes(...)` all filter that column as expected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The main forest-plot table (built via reactable2() from
format_ae_forestly()) renders a per-column filter box for the Adverse
Event column. This is the column where excluding a term is actually
meaningful, since it lists every AE (Rash, Pruritus, Erythema, ...).
Previous commits added the custom filter/search behavior only to the
table-wide search box and to the nested detail table, so typing `!Rash`
or `x !== "Rash"` in the Adverse Event filter fell through to reactable's
default substring matcher: it searched for the literal text and returned
an empty table (even `x === "Rash"` matched nothing).
Attach a custom `filterMethod` to the `name` (Adverse Event) column
definition supporting:
- substring match, with a leading `!` to negate (e.g. `!Rash`), and
- JS expression evaluation when the term references the cell value `x`
(e.g. `x !== "Rash"`, `!x.includes("site")`).
Verified in a headless browser (chromote) against the ae_forestly()
widget produced by the documented pipeline: `Rash` -> 5 AEs, `!Rash` and
`x !== "Rash"` -> all AEs, `x === "Rash"` -> 1, `!x.includes("site")`
-> all non-"Application site *" AEs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The negation/expression search logic was copy-pasted in three places: the table-wide searchMethod and the per-column filterMethod of the nested detail table (R/ae_forestly.R), and the Adverse Event column filter (R/format_ae_forestly.R). Extract a single internal helper, search_filter_js(scope), that emits the reactable::JS() callback for either a per-column filterMethod (scope = "column") or a table-wide searchMethod (scope = "table"). The two share one body: the searched column ids are normalized to an array (`[columnId]` vs `columnIds`), and some()/every() over that array yields the single-cell behavior for a column filter and the any/all-cell behavior for a table search. No behavior change. Verified in a headless browser (chromote) that the Adverse Event column filter and the nested detail table's column filter and search still handle substring, `!` negation, and `x`-expression terms identically. `devtools::test(filter = "ae_forestly")` passes (33 checks). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a user-facing "Searching and filtering" section to ?ae_forestly
explaining the two search styles the interactive table understands beyond
plain substring matching:
- Negation: prefix a term with `!` to exclude matching rows (e.g. `!Rash`).
- Expressions: a term mentioning `x` (the cell value) is evaluated as a
small condition, e.g. `x > 5`, `x >= 18 && x <= 65`, `x !== "Rash"`,
`x.includes("itch")`, `x.startsWith("Application")`.
The section is written for R users who may not know JavaScript: it gives a
two-column table of ready-to-type examples with plain-English meanings and
notes on quoting, `&&`/`||`/`!`, the doubled comparison symbols, and
case-sensitivity. Every example in the table was verified against the
actual search implementation.
Co-Authored-By: Claude Opus 4.8 <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.
Summary
searchMethodto the nested AE listing reactable so users can filter detail rows with either:!to exclude matches — e.g.Rashkeeps matching rows,!Rashhides them.x— e.g.x > 50,x !== "Rash",x.includes("Rash"),!x.includes("Rash").some()for positive tests (row kept if ANY cell matches) andevery()for negation tests (row kept only if EVERY cell satisfies, so rows containing the excluded value are dropped).x, so!Rashandx !== "Rash"no longer collide.Testing
Verified in a headless browser (chromote) against a reactable using the exact
searchMethod:!RashRashx !== "Rash"x > 50!x.includes("Rash")devtools::test(filter = "ae_forestly")passes (30 checks), including a new regression test asserting the customsearchMethodis embedded.Supersedes #134 (same branch, now hosted on Merck/forestly).
🤖 Generated with Claude Code