Skip to content

FEAT: Searchable multi-select filters for Attack History (ADO 7834)#1643

Open
adrian-gavrila wants to merge 10 commits intomicrosoft:mainfrom
adrian-gavrila:adrian-gavrila/attack-history-comboboxes
Open

FEAT: Searchable multi-select filters for Attack History (ADO 7834)#1643
adrian-gavrila wants to merge 10 commits intomicrosoft:mainfrom
adrian-gavrila:adrian-gavrila/attack-history-comboboxes

Conversation

@adrian-gavrila
Copy link
Copy Markdown
Contributor

@adrian-gavrila adrian-gavrila commented Apr 22, 2026

Migrates the Attack History view's Fluent UI Dropdowns to searchable multi-select Comboboxes and expands backend filter semantics to support the new UX. Resolves ADO 7834.

User-visible changes

  • Attack type, converter, operator, and operation filters are now searchable multi-select Comboboxes (was: single-select Dropdowns). Selected-value display shows the first pick with a (+N) suffix when more than one is selected.
  • The converter filter has a new (No converters) option, mutually exclusive with picking specific converters.
  • When 2+ converters are selected, a Match any | Match all Switch controls whether an attack must use any or all of them.
image

REST contract change

  • GET /attacks query param attack_class is renamed to attack_types (repeated, list-valued) to align with the multi-select UI and the already-existing GET /attack-options response field. The only in-tree consumer is the frontend, updated in this PR.

Memory layer additions (non-breaking)

  • MemoryInterface.get_attack_results gains a new attack_classes: Sequence[str] param for OR-matched multi-filtering, plus has_converters: bool | None for explicit presence/absence filtering. The old attack_class: str param is preserved as a deprecated forwarder, and converter_classes=[] keeps its original "no converters" semantic.

Tests and Documentation

Tests added

  • Memory: new tests for attack_classes multi-match, has_converters=True/False, back-compat of singular attack_class, and the ValueError when both singular and plural are passed.
  • Service: has_converters forwarding, match-mode combinations, attack_types list handling.
  • Route: query param rename, repeated attack_types, converter_types_match any/all, has_converters wiring.
  • Frontend: sentinel option, sentinel/converter mutual exclusion, match-mode Switch visibility + emission, multi-select attack type, has_converters API forwarding, conditional converter_types_match emission, multi-select display formatting, outcome filter label persistence.

Documentation: updated relevant docstrings, but no changes to other documentation

Adrian Gavrila and others added 6 commits April 22, 2026 16:12
…ilter"

Add a tri-state has_converters param to get_attack_results. Previously
converter_classes=[] meant "attacks with no converters"; it now means
"no filter". Use has_converters=False for the old behavior.

BREAKING CHANGE: converter_classes=[] no longer filters to zero-converter
attacks. No in-repo callers relied on this.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update the AttackService and REST route to forward the new has_converters
param to memory, and rename the route query param from attack_class (scalar)
to attack_types (repeated) for consistency with the other list-valued
filters.

BREAKING CHANGE: REST query param attack_class renamed to attack_types.
Only in-tree consumer is the frontend, updated in the next commit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace Dropdowns with searchable multi-select Comboboxes for attack
class, converter, operator, and operation filters. The converter
Combobox includes a "(No converters)" sentinel option that maps to
has_converters=false. A match-mode Switch (ANY|ALL) appears when two
or more converters are selected.

Adapts AttackHistory.fetchAttacks to send attack_types (repeated),
converter_types_match, and has_converters to the API.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…] semantic)

Restores the deprecated singular `attack_class` parameter as a forwarder to `attack_classes` and reverts `converter_classes=[]` to its original "no converters" meaning. Library callers see no behavior change; the new `attack_classes`/`has_converters` params remain available for explicit multi/presence filtering.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Multi-select Comboboxes now display "<first> (+N)" instead of always showing the placeholder. Swaps the outcome filter from Dropdown to single-select Combobox so its visible label persists across re-renders triggered by sibling filter changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ce label keys

- Service layer coerces converter_types=[] to 'no filter'; the 'no converters'
  intent is expressed via has_converters=False. Keeps route/service/memory
  semantics consistent.
- memory_interface.get_attack_results strips label keys whose value is an
  empty sequence before emitting the label condition (previously produced a
  strictly-more-restrictive EXISTS predicate).
- Skip the redundant has_converters=True predicate when converter_classes is
  already non-empty.
- Adds unit tests for the empty-sequence/no-identifier branches in the memory
  and backend service layers.
- Fixes two Attack History e2e specs to click the Combobox input (Fluent v9
  multiselect doesn't open the listbox on wrapper click).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@rlundeen2 rlundeen2 self-requested a review April 22, 2026 22:58
- Fluent v9 multiselect Combobox renders items as role=menuitemcheckbox,
  not role=option. Update attack-class and operator filter tests to
  query the correct role.
- Update the mockHistoryAPIs handler to read the renamed attack_types
  (plural, repeatable) query param and to group repeated label keys
  into OR-sets (matches the route's real semantic).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR upgrades the Attack History filtering UX to searchable multi-select controls and updates the backend /attacks filtering contract and memory-layer semantics to support multi-value filtering (including converter presence and match-mode).

Changes:

  • Frontend: migrate history filters from single-select Dropdowns to multi-select Comboboxes, add (No converters) sentinel and Match any | Match all toggle behavior, and update request serialization (attack_types, has_converters, converter_types_match).
  • Backend: rename query param attack_typeattack_types (repeatable), add has_converters and converter_types_match wiring, and expand memory filtering to support attack_classes and OR-within-label-key semantics.
  • Tests: add/adjust unit + e2e tests covering new filter semantics, query parsing, and UI behavior.

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
uv.lock Bumps local editable pyrit dev version.
pyrit/memory/memory_interface.py Adds plural attack_classes, has_converters, label OR-within-key semantics, and deprecates singular attack_class.
pyrit/memory/sqlite_memory.py Implements label OR-within-key filtering for SQLite JSON labels.
pyrit/memory/azure_sql_memory.py Implements label OR-within-key filtering for Azure SQL JSON labels.
pyrit/backend/services/attack_service.py Wires new filter params; adds converter match-mode behavior with optional Python-side filtering.
pyrit/backend/routes/attacks.py Updates /attacks query contract (attack_types, converter_types_match, has_converters) and label grouping.
tests/unit/memory/test_azure_sql_memory.py Adds tests for Azure SQL label condition bind-param behavior.
tests/unit/memory/memory_interface/test_interface_attack_results.py Adds tests for new memory filter semantics (labels OR, attack_classes, has_converters, back-compat).
tests/unit/backend/test_attack_service.py Adds service-layer tests for forwarding/coercion and converter match-mode behavior.
tests/unit/backend/test_api_routes.py Adds route parsing/forwarding tests for repeated params and new query fields.
frontend/src/services/api.ts Updates listAttacks param types and keeps repeated-array query serialization.
frontend/src/components/History/historyFilters.ts Updates filter state model for multi-select + new converter semantics.
frontend/src/components/History/HistoryFiltersBar.tsx Migrates UI controls to multiselect Comboboxes + sentinel + match-mode toggle.
frontend/src/components/History/HistoryFiltersBar.test.tsx Updates/extends UI unit tests for multi-select and new converter behavior.
frontend/src/components/History/AttackHistory.tsx Updates API request construction for new filter shape and params.
frontend/src/components/History/AttackHistory.test.tsx Updates/extends integration tests to validate API forwarding.
frontend/e2e/history.spec.ts Updates e2e tests for multiselect Combobox roles and new query param name.

Comment thread pyrit/memory/azure_sql_memory.py
Comment thread frontend/src/components/History/HistoryFiltersBar.tsx Outdated
Comment thread pyrit/backend/services/attack_service.py
Comment thread pyrit/backend/routes/attacks.py Outdated
- Validate label keys against [A-Za-z0-9_.-]+ allowlist in
  MemoryInterface.get_attack_results before interpolation into backend
  JSON-path expressions, preventing SQL injection via crafted label keys
  (e.g. Azure SQL's text(f\$.{key}) fragment).
- Make multi-select Attack History filters actually searchable: extract
  a SearchableMultiCombobox helper that tracks open + search state,
  filters options by typed text, and shows the formatted 'name (+N)'
  summary only while the popover is closed. Apply the same pattern
  inline to the converter filter (keeps its sentinel OptionGroup).
- Strip empty strings from ?attack_types= query param for symmetry with
  ?converter_types=, and clarify the route-layer comment now that the
  service coerces [] to None.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adrian Gavrila and others added 2 commits April 23, 2026 14:25
Replaces the single dynamic Switch label (which mutated between 'Match any' and 'Match all') with always-visible 'ANY' and 'ALL' text flanking the Switch, preceded by a 'Converters:' prefix that anchors the toggle to the converter filter it governs. Active side is emphasized via bold + stronger foreground so the current state is readable at a glance without mental translation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.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.

2 participants