Skip to content

core&ui: assignment auto complete - #1061

Merged
undefined-moe merged 13 commits into
hydro-dev:masterfrom
TaiRuiQu:master
Oct 10, 2025
Merged

core&ui: assignment auto complete#1061
undefined-moe merged 13 commits into
hydro-dev:masterfrom
TaiRuiQu:master

Conversation

@TaiRuiQu

@TaiRuiQu TaiRuiQu commented Oct 6, 2025

Copy link
Copy Markdown
Contributor

Add auto complete for group and users in contest/homework permission control.

e8bdd2ce2fd9e45880b93fcd9ee98cb3 image

Summary by CodeRabbit

  • New Features

    • Public API to list domain groups with optional name or keyword filters.
    • Multi-select AssignSelectAutoComplete with avatars, names, and group metadata.
    • Assign selector added to the contest editor’s assign field for streamlined selection.
  • Chores

    • Exposed AssignSelectAutoComplete via the shared UI API for reuse.
  • Bug Fixes

    • Robust error handling in autocomplete fetching to avoid failures and keep UI state consistent.

@hydro-dev-bot

hydro-dev-bot Bot commented Oct 6, 2025

Copy link
Copy Markdown

Thank you for your submission, we really appreciate it.
Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.

Comment I have read the CLA Document and I hereby sign the CLA below to sign it.

@coderabbitai

coderabbitai Bot commented Oct 6, 2025

Copy link
Copy Markdown

Walkthrough

Adds DomainApi.groups query to validate permissions and return groups for a domain filtered by exact names or a case-insensitive search. Exposes AssignSelectAutoComplete in packages/ui-default/api.ts. Adds a DOM-attached wrapper class AssignSelectAutoComplete (extends AutoComplete) with a custom value() and global assignment. Introduces a React AssignSelectAutoComplete component that loads users and groups, maps them to unified items, and renders multi-select UI; it is initialized on the contest edit page. Adds try/catch error handling around async fetches in the AutoComplete core to log errors and reset UI state.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • pandadtdyy

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title “core&ui: assignment auto complete” clearly identifies the main change—adding an assignment autocomplete feature spanning core and UI—and is concise and focused on the primary addition.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@TaiRuiQu

TaiRuiQu commented Oct 6, 2025

Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/ui-default/components/autocomplete/AssignSelectAutoComplete.tsx (1)

7-17: Remove hardcoded multi: true to respect caller's configuration.

The constructor hardcodes multi: true in props (line 12) but then spreads options (line 15), allowing the hardcoded value to be overridden. This creates ambiguity about the intended behavior.

Since the generic parameter Multi suggests configurability and the caller in contest_edit.page.ts explicitly passes multi: true, the hardcoded value is redundant.

Apply this diff to remove the redundancy:

   constructor($dom, options: AutoCompleteOptions<Multi> = {}) {
     super($dom, {
       classes: 'assign-select',
       component: AssignSelectAutoCompleteFC,
       props: {
-        multi: true,
         height: 'auto',
       },
       ...options,
     });
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be86224 and 0d5b903.

📒 Files selected for processing (5)
  • packages/hydrooj/src/handler/domain.ts (1 hunks)
  • packages/ui-default/api.ts (1 hunks)
  • packages/ui-default/components/autocomplete/AssignSelectAutoComplete.tsx (1 hunks)
  • packages/ui-default/components/autocomplete/components/AssignSelectAutoComplete.tsx (1 hunks)
  • packages/ui-default/pages/contest_edit.page.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
packages/ui-default/components/autocomplete/AssignSelectAutoComplete.tsx (1)
packages/ui-default/components/autocomplete/index.tsx (1)
  • AutoCompleteOptions (6-20)
packages/ui-default/pages/contest_edit.page.ts (2)
packages/ui-default/api.ts (1)
  • AssignSelectAutoComplete (34-34)
packages/ui-default/components/autocomplete/AssignSelectAutoComplete.tsx (1)
  • AssignSelectAutoComplete (4-22)
packages/hydrooj/src/handler/domain.ts (3)
framework/framework/api.ts (1)
  • Query (29-29)
packages/hydrooj/src/libs.ts (1)
  • Schema (14-14)
packages/hydrooj/src/service/layers/user.ts (1)
  • ctx (5-18)
packages/ui-default/components/autocomplete/components/AssignSelectAutoComplete.tsx (3)
packages/ui-default/components/autocomplete/AssignSelectAutoComplete.tsx (1)
  • AssignSelectAutoComplete (4-22)
packages/ui-default/utils/index.ts (1)
  • api (5-9)
packages/hydrooj/src/interface.ts (2)
  • Udoc (79-93)
  • GDoc (111-116)
🔇 Additional comments (4)
packages/ui-default/pages/contest_edit.page.ts (1)

3-3: LGTM!

The integration of AssignSelectAutoComplete follows the same pattern as existing autocomplete widgets and is properly initialized with multi-select enabled.

Also applies to: 15-15

packages/ui-default/api.ts (1)

27-27: LGTM!

The export follows the established pattern for other autocomplete components.

Also applies to: 34-34

packages/ui-default/components/autocomplete/components/AssignSelectAutoComplete.tsx (2)

43-75: Add explicit type discrimination for keys
The fetchItems implementation uses numeric regex to separate user IDs from group names (line 48), which misclassifies numeric group names and relies on an undocumented key-format contract. Consider prefixing keys (e.g., "user:123", "group:admins"), passing typed items instead of raw keys, or documenting/enforcing non-numeric group names.
Verify that the users endpoint supports the auto parameter with the backend/API spec.


20-42: users API endpoint exists with search support
The users query is defined in packages/hydrooj/src/handler/user.ts with search: Schema.string(), matching the UI’s api('users', { search }) call.

Comment thread packages/hydrooj/src/handler/domain.ts
Comment thread packages/hydrooj/src/handler/domain.ts
@TaiRuiQu
TaiRuiQu requested a review from undefined-moe October 7, 2025 05:33
@TaiRuiQu
TaiRuiQu requested a review from undefined-moe October 7, 2025 10:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (2)
packages/hydrooj/src/handler/domain.ts (1)

440-440: Mark domainId as required in the schema.

The domainId parameter should be marked as required to prevent potential runtime errors when args.domainId is undefined.

Apply this diff:

-            domainId: Schema.string(),
+            domainId: Schema.string().required(),
packages/ui-default/components/autocomplete/components/AssignSelectAutoComplete.tsx (1)

104-114: Remove isRequired from onChange PropType.

The onChange prop is marked as isRequired, but the underlying AutoComplete component makes it optional (defaulting to a no-op). The PropType should reflect this optionality.

Based on past review comments.

Apply this diff:

 AssignSelectAutoComplete.propTypes = {
   width: PropTypes.string,
   height: PropTypes.string,
   listStyle: PropTypes.object,
-  onChange: PropTypes.func.isRequired,
+  onChange: PropTypes.func,
   multi: PropTypes.bool,
   selectedKeys: PropTypes.arrayOf(PropTypes.string),
   allowEmptyQuery: PropTypes.bool,
   freeSolo: PropTypes.bool,
   freeSoloConverter: PropTypes.func,
 };
🧹 Nitpick comments (2)
packages/ui-default/components/autocomplete/components/AssignSelectAutoComplete.tsx (2)

73-89: Simplify the renderItem structure.

The conditional rendering of the avatar creates an asymmetric structure. Consider refactoring to always render a media__left container and conditionally render its content, which improves consistency and maintainability.

Based on past review comments.

Apply this diff:

     renderItem={(item) => (
       <div className="media">
-        {item.type === 'user' ? (
-          <div className="media__left medium">
+        <div className="media__left medium">
+          {item.type === 'user' && (
             <img className="small user-profile-avatar" alt="" src={item.avatarUrl} width="30" height="30" />
-          </div>
-        ) : null}
+          )}
+        </div>
         <div className="media__body medium">
           <div className="assign-select__name">
             {item.name}{item.type === 'user' && item.displayName && ` (${item.displayName})`}
           </div>
           <div className="assign-select__desc">
             {item.type === 'group' ? <>Group • {item.uids?.length || 0} users</> : <>User • UID = {item.key}</>}
           </div>
         </div>
       </div>
     )}

104-114: Consider removing PropTypes in favor of TypeScript.

The component uses both TypeScript types (AutoCompleteProps<AssignItem>) and runtime PropTypes validation. Since TypeScript provides compile-time type safety, PropTypes add maintenance overhead without significant benefit in a TypeScript codebase. Consider removing PropTypes unless runtime validation is specifically required for this component.

If you decide to keep PropTypes, ensure they stay in sync with the TypeScript types.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3bfc19 and 1b121b9.

📒 Files selected for processing (2)
  • packages/hydrooj/src/handler/domain.ts (1 hunks)
  • packages/ui-default/components/autocomplete/components/AssignSelectAutoComplete.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/hydrooj/src/handler/domain.ts (2)
packages/hydrooj/src/model/builtin.ts (2)
  • PERM (9-104)
  • PRIV (242-269)
packages/hydrooj/src/error.ts (1)
  • PermissionError (22-27)
packages/ui-default/components/autocomplete/components/AssignSelectAutoComplete.tsx (3)
packages/hydrooj/src/interface.ts (2)
  • Udoc (79-93)
  • GDoc (111-116)
packages/ui-default/components/autocomplete/AssignSelectAutoComplete.tsx (1)
  • AssignSelectAutoComplete (4-22)
packages/ui-default/utils/index.ts (1)
  • api (5-9)
🔇 Additional comments (1)
packages/ui-default/components/autocomplete/components/AssignSelectAutoComplete.tsx (1)

46-48: Verify backend handling of negative user IDs
The regex /^-?[0-9]+$/ allows negative IDs; confirm that the users API (api('users', { auto: userIds }, …)) correctly processes stringified negative IDs.

Comment thread packages/hydrooj/src/handler/domain.ts
Comment thread packages/hydrooj/src/handler/domain.ts Outdated
@TaiRuiQu
TaiRuiQu requested a review from undefined-moe October 7, 2025 12:48

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
packages/ui-default/components/autocomplete/components/AssignSelectAutoComplete.tsx (2)

51-51: Missing avatarUrl in fetchItems projection.

The fetchItems API call on line 51 requests ['_id', 'uname', 'displayName'] but omits 'avatarUrl', which is used by toUserItem (line 22) and rendered in the UI (line 77). This will cause avatars to be missing when items are fetched by keys.

Apply this diff to include avatarUrl:

       const [users, groups]: [Udoc[], GDoc[]] = await Promise.all([
-        userIds.length > 0 ? api('users', { auto: userIds }, ['_id', 'uname', 'displayName']) : [],
+        userIds.length > 0 ? api('users', { auto: userIds }, ['_id', 'uname', 'displayName', 'avatarUrl']) : [],
         groupNames.length > 0 ? api('groups', { names: groupNames }, ['name', 'uids']) : [],
       ]);

108-108: Remove .isRequired from onChange PropType.

The onChange prop is marked as required, but the underlying AutoComplete component makes it optional (defaulting to a no-op function on line 89 of AutoComplete.tsx). The PropType should match the TypeScript interface.

Apply this diff:

-  onChange: PropTypes.func.isRequired,
+  onChange: PropTypes.func,
🧹 Nitpick comments (1)
packages/ui-default/components/autocomplete/components/AssignSelectAutoComplete.tsx (1)

56-61: Consider visual styling for invalid groups.

Lines 56-61 correctly handle missing/deleted groups by marking them with invalid: true, and line 67 displays "(invalid)" text. However, the invalid state could be more prominent in the rendered item (lines 73-89) to better alert users.

Consider adding visual styling for invalid items:

     renderItem={(item) => (
-      <div className="media">
+      <div className={`media${item.invalid ? ' invalid' : ''}`}>
         {item.type === 'user' && (
           <div className="media__left medium">
             <img className="small user-profile-avatar" alt="" src={item.avatarUrl} width="30" height="30" />
           </div>
         )}
         <div className="media__body medium">
           <div className="assign-select__name">
             {item.name}{item.type === 'user' && item.displayName && ` (${item.displayName})`}
+            {item.invalid && <span className="invalid-badge"> (invalid)</span>}
           </div>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b4461b6 and b070864.

📒 Files selected for processing (3)
  • packages/components/frontend/autocomplete/AutoComplete.tsx (3 hunks)
  • packages/hydrooj/src/handler/domain.ts (2 hunks)
  • packages/ui-default/components/autocomplete/components/AssignSelectAutoComplete.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/hydrooj/src/handler/domain.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/ui-default/components/autocomplete/components/AssignSelectAutoComplete.tsx (4)
packages/hydrooj/src/interface.ts (2)
  • Udoc (79-93)
  • GDoc (111-116)
packages/ui-default/components/autocomplete/AssignSelectAutoComplete.tsx (1)
  • AssignSelectAutoComplete (4-22)
packages/components/frontend/autocomplete/AutoComplete.tsx (2)
  • AutoCompleteHandle (38-50)
  • AutoCompleteProps (12-36)
packages/ui-default/utils/index.ts (1)
  • api (5-9)
🔇 Additional comments (4)
packages/components/frontend/autocomplete/AutoComplete.tsx (3)

115-124: LGTM! Robust error handling added.

The try/catch block properly handles query failures by logging errors and resetting the UI state (empty list, null selection). This prevents the component from breaking when the query API fails.


144-146: LGTM! Appropriate error handling for effect.

The catch handler correctly logs fetch failures without re-throwing, which is appropriate for a useEffect hook. This prevents unhandled promise rejections when pre-loading selected items.


298-304: LGTM! Graceful paste failure handling.

The try/catch ensures that paste failures don't break the component. Users can continue interacting with the autocomplete even if batch fetching fails.

packages/ui-default/components/autocomplete/components/AssignSelectAutoComplete.tsx (1)

46-46: Confirm support for negative user IDs
The regex /^-?[0-9]+$/ matches negative numbers; if negative IDs aren’t intended, restrict to /^[0-9]+$/.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/components/frontend/autocomplete/AutoComplete.tsx (1)

115-124: Consider structured error handling for production.

While the error logging is functional, consider these optional improvements:

  1. Structured logging: Replace console.error with a proper logging service for production monitoring
  2. Error callback API: Add an onError?: (error: Error, context: string) => void prop to allow parent components to handle errors (show notifications, track metrics, etc.)
  3. Error recovery: For the paste operation specifically, consider partial success handling (add items that succeeded, notify about failures)

Example API enhancement:

export interface AutoCompleteProps<Item> {
  // ... existing props
  onError?: (error: Error, context: 'query' | 'fetch' | 'paste') => void;
}

Then use it in error handlers:

 } catch (e) {
   console.error('Failed to query items', e);
+  props.onError?.(e, 'query');
   setItemList([]);
   setCurrentItem(null);
 }

Also applies to: 144-146, 298-304

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b070864 and a0e634b.

📒 Files selected for processing (2)
  • packages/components/frontend/autocomplete/AutoComplete.tsx (3 hunks)
  • packages/hydrooj/src/handler/domain.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/hydrooj/src/handler/domain.ts
🔇 Additional comments (2)
packages/components/frontend/autocomplete/AutoComplete.tsx (2)

115-124: LGTM! Proper error handling with state cleanup.

The try-catch block correctly handles query failures by logging the error and resetting the UI state. The cache is only updated after a successful query, preventing inconsistent state.


144-146: LGTM! Prevents unhandled promise rejection.

The catch handler appropriately logs fetch failures for pre-selected items. The existing code already handles missing items gracefully by displaying the key when an item isn't in the cache (line 274).

Comment on lines +298 to +304
try {
const fetched = await props.fetchItems(ids);
for (const item of fetched) valueCache[itemKey(item)] = item;
setSelectedKeys([...selectedKeys, ...fetched.map((val) => itemKey(val))]);
} catch (err) {
console.error('Failed to fetch items on paste', err);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Silent failure on paste may confuse users.

When fetchItems fails during a paste operation, no items are added and users receive no feedback about the failure. This can be confusing, especially when users paste multiple comma-separated values expecting them to appear.

Consider providing user-visible feedback when paste operations fail, such as:

  • Displaying a toast notification
  • Adding invalid items with a visual indicator
  • Showing an error state in the input

Example improvement:

 try {
   const fetched = await props.fetchItems(ids);
   for (const item of fetched) valueCache[itemKey(item)] = item;
   setSelectedKeys([...selectedKeys, ...fetched.map((val) => itemKey(val))]);
 } catch (err) {
   console.error('Failed to fetch items on paste', err);
+  // Consider: Show toast notification or set error state
+  // e.g., props.onError?.('Failed to load pasted items');
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
const fetched = await props.fetchItems(ids);
for (const item of fetched) valueCache[itemKey(item)] = item;
setSelectedKeys([...selectedKeys, ...fetched.map((val) => itemKey(val))]);
} catch (err) {
console.error('Failed to fetch items on paste', err);
}
try {
const fetched = await props.fetchItems(ids);
for (const item of fetched) valueCache[itemKey(item)] = item;
setSelectedKeys([...selectedKeys, ...fetched.map((val) => itemKey(val))]);
} catch (err) {
console.error('Failed to fetch items on paste', err);
// Consider: Show toast notification or set error state
// e.g., props.onError?.('Failed to load pasted items');
}
🤖 Prompt for AI Agents
In packages/components/frontend/autocomplete/AutoComplete.tsx around lines
298-304 the paste handler silently logs fetchItems errors to console which
leaves users unaware that their pasted items failed to load; modify the catch
block to surface a user-visible error (e.g., trigger the app's
toast/notification system with a clear message, or add the pasted keys to the
selection with an "invalid" flag that renders a visual error state in the
list/input), and ensure valueCache and selectedKeys are not left in a
partial/incorrect state on failure (use a local temporary array and only update
state on success, or roll back on error) so the UI reflects the failure and
guides the user to retry or correct input.

@undefined-moe
undefined-moe merged commit 564ff1b into hydro-dev:master Oct 10, 2025
5 checks passed
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