lablup / lablup/backend.ai-webui

Property filter: declarative entity picker for opaque-id properties (search by email, filter by UUID) on PowerSearch

Open
#9,088 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
133
Forks
81
Avg merge
1d 12h
Merged PRs (30d)
355

Description

## Problem

`BAIGraphQLPropertyFilter` / `BAIPropertyFilter` let a call site replace the built-in value editor with a controlled control via `renderInput` (FR-2786 / FR-3011 / FR-3258, "id-based field as name-searchable Select" FR-3150). The RBAC role list uses it so a user can _search by email_ while the filter serializes the _user UUID_ (`assignedUser.userId`).

After the to-astryx ticket-28 rebuild onto Astryx `PowerSearch`, that escape hatch still works for the _first_ selection but is degraded:

1. **Editing an existing token opens an empty control.** Astryx supplies the staged value to a `custom` operator value's `Editor` (`PowerSearchValueEditor.tsx:606-620`), but `BAIPowerSearchAdapters.tsx:153` destructures `({ onChange })` only and drops `value` and `isDisabled`. `FilterRenderInput`'s argument object has no slot to forward them into, and every call site pins `value={null`}. Because `isSaveDisabled = !partialFilter.value`, Apply stays enabled and silently re-commits the previous UUID.
1. `onChange(null)` is swallowed upstream (`PowerSearchValueEditor.tsx:613-616`), so a staged value cannot be cleared.
1. **The token shows a raw UUID after any reload or shared link.** The value -> label map is an in-memory `useRef` (`BAIGraphQLPropertyFilter.tsx:720`), and the URL carries only the GraphQL filter object. Not a regression — the antd implementation behaved the same — but it is the thing that makes the feature feel broken.
1. **Zero test coverage.** Both fixtures stub `renderInput: () => null`.

There is also no declarative API: every call site hand-rolls ~18 lines of JSX to wire a picker.

## Approach

Add an additive, declarative `entitySource` mode to both filters' `FilterProperty`, and fix the `renderInput` defects (keep `renderInput` — it is part of the frozen antd-shaped surface and serves cases where value == label).

Native PowerSearch `{type: 'entity_list'`} was evaluated and rejected:

- it is `Tokenizer`-backed and **multi-only** with no arity cap reachable from config, so it cannot serve `fixedOperator: 'equals'` without silent truncation;
- its one unique advantage — the label stored inside `FilterValueEntityList` — buys nothing here, because what this project serializes is the GraphQL filter object (`RoleUserNestedFilter.userId` is a `UUIDFilter`, no label slot) or the queryfilter DSL string, not the PowerSearch `FilterValue`;
- `EntityListEditor` does not forward `hasEntriesOnFocus` or `isDisabled`, so the picker is blank until the user types.

Instead the entity editor mounts the same Astryx primitives `EntityListEditor` itself renders — `Typeahead` for single-value operators, `Tokenizer` for `in` / `notIn` — keeping the combobox/listbox roles, keyboard model, IME guard, debounce, loading state, `cancel()` and race guard, and adding single-select, entries-on-focus and `isDisabled`.

Arity is derived from the **operator**, never the property, so RBAC keeps `fixedOperator: 'equals'` and its URL bytes are unchanged. No manager-behaviour gamble on `UUIDFilter.in`, no URL migration.

## Scope

- `BAIPowerSearchAdapters.tsx` — `FilterEntity` / `FilterEntitySource` types, `useEntityLabelCache` (state-backed so a late resolve repaints tokens), the entity editor, `useEntityEditors`; forward `value` / `isDisabled` into `renderInput`.
- `BAIGraphQLPropertyFilter.tsx` / `BAIPropertyFilter.tsx` — `entitySource` field, editor precedence (`renderInput` wins), entity branch in `conditionToTokenValue` placed **before** the list-operator branch (fixes a latent mismatch), `contentSearchFieldKey` auto-pick exclusion, shared label cache + resolve effect.
- New BUI hook `useBAIUserEntitySource` — `fetchQuery` over `user_nodes` for search / bootstrap / batch id->email resolve.
- `RBACManagementPage.tsx` — migrate `assignedUser.userId` off `renderInput` (net -18 lines at the call site).
- Tests (round-trip byte-identity, both arities, precedence, label resolution incl. a rejecting resolver) and stories.

Follow-up, not in this change: `useBAIAdminProjectEntitySource` for `AdminComputeSessionListPage` / the other `projectId` properties; PowerSearch bar-typeahead value suggestions for entity fields (`usePowerSearchSource.ts` returns `[]` for both `custom` and `entity_list` and its `search` is synchronous — an upstream Astryx change).

## Acceptance

- Pasting an RBAC URL that carries `assignedUser.userId.equals=` into a fresh tab shows the **email** on the token, not the UUID.
- Clicking an existing entity token opens the picker **pre-populated** with the current selection.
- The GraphQL filter object emitted for a single-value entity property is byte-identical to today's.

JIRA Issue: FR-3691

Contributor guide

No contributing guide indexed for this repository

Research direction

Start in BAIPowerSearchAdapters.tsx by tracing PowerSearchValueEditor.tsx and the existing renderInput path, then inspect FilterProperty and token conversion in BAIGraphQLPropertyFilter.tsx and BAIPropertyFilter.tsx. Implement the entity source and useBAIUserEntitySource, migrate RBACManagementPage.tsx, and add the described round-trip, arity, precedence, label-resolution, and story coverage. Done means existing tokens reopen with their value, URLs show resolved emails, and emitted filters remain byte-identical.

Written by the indexing model from the issue text.

Assessment

Tech stack
graphql, react, typescript
Domain
api, frontend, testing
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.