Skip to content

Conversation

@Kavirubc
Copy link

@Kavirubc Kavirubc commented Dec 18, 2025

Summary

  • Add server-side search and filtering for the ListAgents API
  • Move from client-side filtering to efficient database queries with PostgreSQL ILIKE
  • Add debounced search input (300ms) in frontend to reduce API calls

Changes

Backend (amp-api)

  • Add AgentFilter model with search, sorting, and pagination options
  • Add ListAgentsWithFilter repository method with case-insensitive search
  • Update service and controller to parse and use filter query parameters

Frontend (console)

  • Extend ListAgentsQuery type with search parameters
  • Add debounced search state in AgentsList component
  • Fix redirect behavior when search returns no results

Database Schema Requirements

Required for filtering (this PR)

The provisioning_type column must exist on the agents table:

-- Add provisioning_type column if missing
ALTER TABLE agents ADD COLUMN IF NOT EXISTS provisioning_type VARCHAR(100) NOT NULL DEFAULT 'internal';

-- Add constraint if missing
ALTER TABLE agents ADD CONSTRAINT IF NOT EXISTS provisioning_type_enum 
  CHECK (provisioning_type IN ('internal', 'external'));

Required for internal agent creation

The internal_agents table must exist with proper defaults:

-- If internal_agents table exists but has NOT NULL constraint issues:
ALTER TABLE internal_agents ALTER COLUMN agent_type SET DEFAULT 'external';
ALTER TABLE internal_agents ALTER COLUMN agent_subtype SET DEFAULT '';
ALTER TABLE internal_agents ALTER COLUMN language SET DEFAULT 'python';

Note: New databases using the full migration set (migrations 005 and 007) will have these tables and columns correctly configured.

API Changes

New query parameters for GET /orgs/{org}/projects/{project}/agents:

Parameter Type Description
search string Case-insensitive search across name, displayName, description
provisioningType string Filter by type: "internal" or "external"
sortBy string Sort field: "name", "createdAt", "updatedAt" (default: "createdAt")
sortOrder string Sort direction: "asc" or "desc" (default: "desc")

Test Plan

  • Create multiple agents with different names/descriptions
  • Type in search box - should filter agents by name, displayName, description
  • Search with no results should show empty state, not redirect to add agent page
  • Verify debounce works (no API call per keystroke)
  • Verify provisioningType filter works via API

Closes #68

Summary by CodeRabbit

  • New Features

    • Search with 300ms debounce on agent listings (no longer disabled when list is empty)
    • Server-side filtering: search, provisioning type, sort field and sort order
    • Sorting options (name, created/updated time) with asc/desc
    • Pagination now respects server-side filters for accurate counts
  • Behavior Changes

    • Navigation/redirects honor active searches so users aren’t redirected away when results are filtered

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 18, 2025

Walkthrough

Server-side search, filtering, sorting, and pagination were added to ListAgents. Controllers parse query params into a new AgentFilter model, services accept that filter, repositories implement DB-level filtering and return total counts, and the frontend sends debounced search queries instead of client-side filtering.

Changes

Cohort / File(s) Summary
Backend: new filter model
agent-manager-service/models/agent_filter.go
Adds AgentFilter with fields Search, ProvisioningType, SortBy, SortOrder, Limit, Offset; default values and validation helpers plus constants for sort fields, orders, and provisioning types.
Backend: controller parsing
agent-manager-service/controllers/agent_controller.go
Parses search, provisioningType, sortBy, sortOrder, limit, offset from query string, validates them via models helpers, constructs AgentFilter, and passes it to service ListAgents.
Backend: repository query
agent-manager-service/repositories/agent.go
Adds ListAgentsWithFilter to perform GORM-level search (ILIKE across name/display_name/description), provisioning type filter, sorting (name/created_at/updated_at), preloading details, pagination, and returns agents plus total count.
Backend: service API change
agent-manager-service/services/agent_manager.go
Updates ListAgents to accept models.AgentFilter instead of limit/offset, delegates to repository ListAgentsWithFilter, converts results to responses, and returns total.
Frontend: types
console/workspaces/libs/types/src/api/agents.ts
Replaces ListAgentsQuery alias with interface extending ListQuery, adding optional search, provisioningType, sortBy, sortOrder; adds AgentSortBy type.
Frontend: UI and integration
console/workspaces/pages/overview/src/AgentsList/AgentsList.tsx
Adds 300ms debounced search, sends search to API via useListAgents, removes client-side filtering, adjusts redirect guard to respect active search, and re-enables search input.
sequenceDiagram
    participant User
    participant Frontend as AgentsList
    participant API as AgentController
    participant Service as AgentManager
    participant Repo as AgentRepository
    participant DB

    User->>Frontend: Type search/filter
    Frontend->>Frontend: Debounce (300ms)
    Frontend->>API: GET /agents?search=...&sortBy=...&sortOrder=...
    API->>Service: ListAgents(ctx, org, proj, AgentFilter)
    Service->>Repo: ListAgentsWithFilter(ctx, orgId, projId, AgentFilter)
    Repo->>DB: SELECT COUNT(*) WHERE (search/type)
    DB-->>Repo: totalCount
    Repo->>DB: SELECT ... WHERE (search/type) ORDER BY ... LIMIT/OFFSET
    DB-->>Repo: agents + details
    Repo-->>Service: agents, totalCount
    Service->>API: AgentResponse[], totalCount
    API-->>Frontend: {agents, total}
    Frontend-->>User: Render filtered list
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review GORM query construction in agent-manager-service/repositories/agent.go for correct ILIKE usage, SQL injection safety, and proper preloading.
  • Confirm ListAgents signature updates are applied consistently and no callers remain with old signature.
  • Validate controller parameter validation logic (allowed empty values) and defaulting behavior.
  • Check frontend debounce correctness and that pagination/redirect logic handles active searches.

Poem

🐇 I dug a tunnel through the agent rows,
Sent queries to the garden where the SQL grows,
Debounced my hops, let the DB propose,
Carrots sorted, counted — oh how it glows!
Hooray, the list returns in tidy rows 🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Title accurately describes the main change: adding server-side search and filtering for agents list.
Description check ✅ Passed Description covers purpose, changes, database requirements, API changes, and test plan. Most required template sections are addressed, though some optional sections (marketing, training, certification) are not included.
Linked Issues check ✅ Passed All objectives from issue #68 are met: query parameters added (search, provisioningType, sortBy, sortOrder), database-level filtering implemented, total count returned, debouncing added to frontend.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing issue #68 requirements: backend filtering model, repository updates, service modifications, and frontend debounced search implementation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ 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.

@Kavirubc Kavirubc force-pushed the feat/agent-search-filter branch from 5678e71 to 8c1344d Compare December 18, 2025 16:40
@Kavirubc Kavirubc marked this pull request as ready for review December 18, 2025 16:42
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
console/workspaces/libs/types/src/api/agents.ts (1)

65-70: LGTM with optional type strengthening suggestion.

The interface correctly extends ListQuery with optional filter fields. The sortOrder field is well-typed as a union type.

Optionally, consider adding union types for sortBy and provisioningType to match the sortOrder pattern:

🔎 View suggested type improvements:
 export interface ListAgentsQuery extends ListQuery {
   search?: string;
-  provisioningType?: string;
-  sortBy?: string;
+  provisioningType?: 'internal' | 'external';
+  sortBy?: 'name' | 'createdAt' | 'updatedAt';
   sortOrder?: 'asc' | 'desc';
 }

This would provide better type safety and IDE autocomplete support on the frontend.

agent-manager-service/repositories/agent.go (1)

67-75: Clarify that wildcards in user input should be escaped for correct pattern matching behavior.

The ILIKE query is safe from SQL injection via GORM's parameterization, but wildcards (% and _) in filter.Search are not escaped and will be treated as pattern wildcards. A user entering % would return all records. Consider escaping these characters using PostgreSQL's ESCAPE clause or sanitizing the input before concatenation.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad678de and 8c1344d.

📒 Files selected for processing (6)
  • agent-manager-service/controllers/agent_controller.go (2 hunks)
  • agent-manager-service/models/agent_filter.go (1 hunks)
  • agent-manager-service/repositories/agent.go (2 hunks)
  • agent-manager-service/services/agent_manager.go (3 hunks)
  • console/workspaces/libs/types/src/api/agents.ts (1 hunks)
  • console/workspaces/pages/overview/src/AgentsList/AgentsList.tsx (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-18T16:41:56.601Z
Learnt from: hanzjk
Repo: wso2/ai-agent-management-platform PR: 70
File: agent-manager-service/services/agent_manager.go:978-980
Timestamp: 2025-12-18T16:41:56.601Z
Learning: In the agent-manager-service, enforce that external agents cannot have a SubType; only internal/managed agents may have SubType (e.g., chat-api, custom-api). When converting external agents to AgentResponse, the Type payload should include only the Type field and should not expose SubType. Implement this in the conversion/serialization path (likely in services/agent_manager.go) and update related DTO mappings or serializers. Add unit tests to verify internal vs external agent handling and ensure SubType is omitted for external agents.

Applied to files:

  • agent-manager-service/services/agent_manager.go
  • agent-manager-service/models/agent_filter.go
  • agent-manager-service/controllers/agent_controller.go
  • agent-manager-service/repositories/agent.go
🧬 Code graph analysis (5)
console/workspaces/libs/types/src/api/agents.ts (1)
console/workspaces/libs/types/src/api/common.ts (1)
  • ListQuery (27-30)
agent-manager-service/services/agent_manager.go (4)
agent-manager-service/models/agent_filter.go (1)
  • AgentFilter (20-27)
agent-manager-service/models/agent.go (1)
  • AgentResponse (27-37)
agent-manager-service/spec/model_agent_response.go (1)
  • AgentResponse (22-33)
agent-manager-service/repositories/agent.go (1)
  • AgentRepository (30-39)
agent-manager-service/models/agent_filter.go (1)
console/workspaces/libs/types/src/api/agents.ts (1)
  • ProvisioningType (38-38)
agent-manager-service/controllers/agent_controller.go (2)
agent-manager-service/models/agent_filter.go (1)
  • AgentFilter (20-27)
agent-manager-service/middleware/jwtassertion/auth.go (1)
  • GetTokenClaims (80-86)
agent-manager-service/repositories/agent.go (4)
agent-manager-service/models/agent_filter.go (1)
  • AgentFilter (20-27)
agent-manager-service/models/agent.go (1)
  • Agent (58-70)
agent-manager-service/db/tx_utils.go (1)
  • DB (31-46)
console/workspaces/libs/types/src/api/agents.ts (1)
  • ProvisioningType (38-38)
🔇 Additional comments (5)
console/workspaces/pages/overview/src/AgentsList/AgentsList.tsx (3)

103-112: LGTM! Clean debounced search implementation.

The debounced search with a 300ms delay is well-implemented with proper cleanup. This will reduce API calls while providing a responsive user experience.


130-133: Backend search integration correctly implemented.

The search parameter is properly passed to the API with debouncedSearch || undefined, ensuring empty strings are not sent as query parameters.


172-189: Redirect guard correctly prevents navigation during search.

The addition of !debouncedSearch at line 179 prevents automatic redirect to the new agent page when search returns no results, showing the empty state instead. This is the correct UX behavior.

agent-manager-service/services/agent_manager.go (1)

129-165: LGTM! Service layer correctly delegates filtering to repository.

The signature change from separate limit/offset parameters to a single filter object simplifies the API and allows for future filter extensions without changing signatures. The implementation correctly delegates to ListAgentsWithFilter and returns both results and total count.

Note: This service method assumes the filter has been validated by the controller. Ensure the controller at agent-manager-service/controllers/agent_controller.go validates filter parameters before calling this method (see my comment on that file).

agent-manager-service/repositories/agent.go (1)

101-106: Pagination logic applies Limit even when zero.

Lines 101-102 apply Limit(filter.Limit) even when filter.Limit is 0. In GORM, Limit(0) returns no results. The check should be if filter.Limit > 0 to match the Offset check pattern and align with typical pagination behavior where Limit=0 might mean "no limit."

🔎 Apply this diff:
 	// Apply pagination
-	if filter.Limit > 0 {
+	if filter.Limit > 0 {
 		query = query.Limit(filter.Limit)
 	}
 	if filter.Offset > 0 {
 		query = query.Offset(filter.Offset)
 	}

Wait, the code already checks > 0. Let me re-read...

Actually, the existing code at line 101 already checks if filter.Limit > 0, so this is correct. The Offset check at line 104 also uses > 0, which means Offset=0 is the default (start from beginning).

This is actually fine. Let me reconsider - this is correct as-is.

Actually, upon closer inspection, the existing logic at lines 101-106 is correct. My initial assessment was wrong.

Comment on lines +19 to +27
// AgentFilter holds filter options for listing agents
type AgentFilter struct {
Search string // search in name, displayName, description
ProvisioningType string // "internal", "external"
SortBy string // "name", "createdAt", "updatedAt"
SortOrder string // "asc", "desc"
Limit int
Offset int
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Consider adding validation or using constants for filter values.

The AgentFilter struct accepts string values for SortBy, SortOrder, and ProvisioningType without validation. Invalid values will be silently ignored or cause unexpected behavior in the repository layer.

Consider:

  1. Define constants for valid values (e.g., SortByName, SortByCreatedAt, SortOrderAsc, ProvisioningTypeInternal)
  2. Add a Validate() method to the AgentFilter type to check field values before database queries
  3. Return validation errors early in the controller layer

This will improve type safety and provide better error messages to API consumers.

🤖 Prompt for AI Agents
In agent-manager-service/models/agent_filter.go around lines 19 to 27, the
AgentFilter struct accepts free-form strings for SortBy, SortOrder, and
ProvisioningType which can lead to silent failures; define typed constants for
allowed values (e.g., SortByName, SortByCreatedAt, SortOrderAsc/Desc,
ProvisioningTypeInternal/External), add a Validate() method on AgentFilter that
checks these fields (and limits/offset bounds) returning a descriptive error
when a value is invalid, and update callers (controllers) to call Validate() and
return the validation error to clients before proceeding to repository queries.

⚠️ Potential issue | 🟠 Major

Naming inconsistency between default value and expected format.

The comment on line 23 documents SortBy values as camelCase ("name", "createdAt", "updatedAt"), but DefaultAgentFilter() at line 32 uses snake_case "created_at". The repository at lines 84-92 of agent-manager-service/repositories/agent.go validates against camelCase values and maps them to snake_case column names.

This mismatch will cause the default filter to skip the switch statement and fall through to "created_at" being used directly as the sort column, which works accidentally but is inconsistent with the documented API contract.

🔎 Apply this diff to align the default with the expected format:
 func DefaultAgentFilter() AgentFilter {
 	return AgentFilter{
-		SortBy:    "created_at",
+		SortBy:    "createdAt",
 		SortOrder: "desc",
 		Limit:     20,
 		Offset:    0,
 	}
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In agent-manager-service/models/agent_filter.go around lines 19 to 27 (and
DefaultAgentFilter at line 32), the SortBy default uses snake_case
("created_at") while the documented/validated values are camelCase ("name",
"createdAt", "updatedAt"); update the default SortBy to "createdAt" (or adjust
the comment/validation to match) so the default follows the API contract and
will be handled by the repository's switch that maps camelCase to snake_case
column names.

- Add validation constants for SortBy, SortOrder, ProvisioningType
- Add IsValidSortBy, IsValidSortOrder, IsValidProvisioningType helpers
- Fix DefaultAgentFilter to use camelCase 'createdAt' (was snake_case)
- Fix SQL injection risk by using constants in ORDER BY clause
- Add controller validation with proper error responses

Addresses CodeRabbit review feedback on PR wso2#71
- Add AgentSortBy type for sortBy field
- Use ProvisioningType for provisioningType field
- Improves type safety and IDE autocomplete

Addresses CodeRabbit nitpick on PR wso2#71
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 (1)
agent-manager-service/repositories/agent.go (1)

82-99: Consider using GORM's Order method directly for clarity.

While the current implementation is safe (sortColumn is validated via switch, sortOrder via constant), using fmt.Sprintf for SQL construction is less clear than using GORM's methods directly.

🔎 Apply this diff for clearer sorting:
 	// Apply sorting - map camelCase API values to snake_case DB columns
 	sortColumn := "created_at"
 	switch filter.SortBy {
 	case models.SortByName:
 		sortColumn = "name"
 	case models.SortByUpdatedAt:
 		sortColumn = "updated_at"
 	case models.SortByCreatedAt:
 		sortColumn = "created_at"
 	}
 
-	// Validate sortOrder to prevent SQL injection - only allow known values
-	sortOrder := "DESC"
-	if filter.SortOrder == models.SortOrderAsc {
-		sortOrder = "ASC"
+	// Apply sort order using GORM's Order method
+	if filter.SortOrder == models.SortOrderAsc {
+		query = query.Order(sortColumn + " ASC")
+	} else {
+		query = query.Order(sortColumn + " DESC")
 	}
-
-	query = query.Order(fmt.Sprintf("%s %s", sortColumn, sortOrder))

This approach avoids string formatting for SQL and makes the intent clearer, as suggested in the previous review.

🧹 Nitpick comments (1)
agent-manager-service/models/agent_filter.go (1)

35-43: Consider adding a Validate() method to the AgentFilter struct.

While validation helpers exist (lines 55-68), encapsulating validation in a method on AgentFilter would improve the API and ensure consistent validation across all callers.

🔎 Add a Validate method:
+// Validate checks if all filter fields have valid values
+func (f AgentFilter) Validate() error {
+	if !IsValidSortBy(f.SortBy) {
+		return fmt.Errorf("invalid sortBy value: %s (expected: name, createdAt, or updatedAt)", f.SortBy)
+	}
+	if !IsValidSortOrder(f.SortOrder) {
+		return fmt.Errorf("invalid sortOrder value: %s (expected: asc or desc)", f.SortOrder)
+	}
+	if !IsValidProvisioningType(f.ProvisioningType) {
+		return fmt.Errorf("invalid provisioningType value: %s (expected: internal or external)", f.ProvisioningType)
+	}
+	if f.Limit < 0 {
+		return fmt.Errorf("limit must be non-negative")
+	}
+	if f.Offset < 0 {
+		return fmt.Errorf("offset must be non-negative")
+	}
+	return nil
+}

Then update the controller to call filter.Validate() and return the error to API consumers, providing better error messages.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c1344d and 253eeb2.

📒 Files selected for processing (4)
  • agent-manager-service/controllers/agent_controller.go (2 hunks)
  • agent-manager-service/models/agent_filter.go (1 hunks)
  • agent-manager-service/repositories/agent.go (2 hunks)
  • console/workspaces/libs/types/src/api/agents.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • agent-manager-service/controllers/agent_controller.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-18T16:41:56.601Z
Learnt from: hanzjk
Repo: wso2/ai-agent-management-platform PR: 70
File: agent-manager-service/services/agent_manager.go:978-980
Timestamp: 2025-12-18T16:41:56.601Z
Learning: In the agent-manager-service, enforce that external agents cannot have a SubType; only internal/managed agents may have SubType (e.g., chat-api, custom-api). When converting external agents to AgentResponse, the Type payload should include only the Type field and should not expose SubType. Implement this in the conversion/serialization path (likely in services/agent_manager.go) and update related DTO mappings or serializers. Add unit tests to verify internal vs external agent handling and ensure SubType is omitted for external agents.

Applied to files:

  • agent-manager-service/repositories/agent.go
  • agent-manager-service/models/agent_filter.go
🧬 Code graph analysis (3)
agent-manager-service/repositories/agent.go (4)
agent-manager-service/models/agent_filter.go (4)
  • AgentFilter (36-43)
  • SortByUpdatedAt (24-24)
  • SortByCreatedAt (23-23)
  • SortOrderAsc (27-27)
agent-manager-service/models/agent.go (1)
  • Agent (58-70)
agent-manager-service/db/tx_utils.go (1)
  • DB (31-46)
console/workspaces/libs/types/src/api/agents.ts (1)
  • ProvisioningType (38-38)
console/workspaces/libs/types/src/api/agents.ts (1)
console/workspaces/libs/types/src/api/common.ts (1)
  • ListQuery (27-30)
agent-manager-service/models/agent_filter.go (1)
console/workspaces/libs/types/src/api/agents.ts (1)
  • ProvisioningType (38-38)
🔇 Additional comments (9)
console/workspaces/libs/types/src/api/agents.ts (1)

64-73: LGTM! Clean type definitions aligned with backend.

The interface properly extends ListQuery and adds optional filter fields that match the backend AgentFilter model. The AgentSortBy type and inline union types ensure type safety for valid filter values.

agent-manager-service/repositories/agent.go (5)

32-32: LGTM! Appropriate method signature.

The new method signature properly extends the interface to support filtering while maintaining consistency with existing methods and returning the total count needed for pagination.


66-75: LGTM! Safe parameterized queries.

The search and provisioning type filters correctly use parameterized queries to prevent SQL injection. The ILIKE operator provides case-insensitive search across the specified fields.

Note: ILIKE is PostgreSQL-specific. This is acceptable given the PR summary mentions PostgreSQL, but ensure the database requirement is documented.


77-80: LGTM! Correct count query placement.

The total count is correctly calculated after filters but before pagination, providing accurate pagination metadata.


101-107: LGTM! Correct pagination implementation.

Pagination is correctly applied after counting and sorting, with appropriate conditional checks.


109-115: LGTM! Proper query execution and error handling.

The method correctly preloads the AgentDetails relation and returns both the filtered agents and total count with appropriate error handling.

agent-manager-service/models/agent_filter.go (3)

19-33: LGTM! Well-defined constants aligned with frontend.

The constants provide type-safe values for filter options and align perfectly with the frontend TypeScript types, ensuring API contract consistency.


45-53: LGTM! Sensible defaults with constants.

The default filter uses the proper constants (SortByCreatedAt, SortOrderDesc) and provides reasonable pagination defaults. The past review issue regarding naming inconsistency has been resolved.


55-68: LGTM! Correct validation helpers.

The validation functions properly check against defined constants and correctly allow empty strings for optional filters. They provide the building blocks for validation, though a Validate() method on the struct (as suggested earlier) would provide better encapsulation.

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.

Add server-side search and filtering to ListAgents API

1 participant