mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-09 03:17:58 +00:00
searching finds items in components of a pcakage and shows which ones match
This commit is contained in:
parent
81304a22ea
commit
dee7510ed7
7 changed files with 159 additions and 82 deletions
|
|
@ -101,9 +101,10 @@ describe("Package Manager Integration", () => {
|
|||
expect(dataValidator).toBeDefined()
|
||||
expect(dataValidator?.metadata?.description).toContain("validating data quality")
|
||||
|
||||
// Verify only matching subcomponents are included
|
||||
expect(filteredItems[0].items?.length).toBe(1)
|
||||
expect(filteredItems[0].items?.[0].metadata?.name).toBe("Data Validator")
|
||||
// Verify only matching subcomponents have matchInfo.matched = true
|
||||
const matchingSubcomponents = filteredItems[0].items?.filter((item) => item.matchInfo?.matched) || []
|
||||
expect(matchingSubcomponents.length).toBe(1)
|
||||
expect(matchingSubcomponents[0].metadata?.name).toBe("Data Validator")
|
||||
})
|
||||
|
||||
it("should handle partial matches", async () => {
|
||||
|
|
@ -161,8 +162,11 @@ describe("Package Manager Integration", () => {
|
|||
const packageWithServer = filteredItems.find((item) => item.type === "package")
|
||||
expect(packageWithServer).toBeDefined()
|
||||
expect(packageWithServer?.name).toBe("Data Platform Package")
|
||||
expect(packageWithServer?.items?.length).toBe(1)
|
||||
expect(packageWithServer?.items?.[0].metadata?.name).toBe("Data Validator")
|
||||
|
||||
// Count how many subcomponents have matchInfo.matched = true
|
||||
const matchingSubcomponents = packageWithServer?.items?.filter((item) => item.matchInfo?.matched) || []
|
||||
expect(matchingSubcomponents.length).toBe(1)
|
||||
expect(matchingSubcomponents[0].metadata?.name).toBe("Data Validator")
|
||||
|
||||
// Verify excluded items
|
||||
const allItems = [...templateItems]
|
||||
|
|
|
|||
|
|
@ -38,8 +38,11 @@ describe("Package Manager with Real Data", () => {
|
|||
// Should find Data Platform Package containing Data Validator
|
||||
expect(filteredItems.length).toBe(1)
|
||||
expect(filteredItems[0].name).toBe("Data Platform Package")
|
||||
expect(filteredItems[0].items?.length).toBe(1)
|
||||
expect(filteredItems[0].items?.[0].metadata?.name).toBe("Data Validator")
|
||||
|
||||
// Count how many subcomponents have matchInfo.matched = true
|
||||
const matchingSubcomponents = filteredItems[0].items?.filter((item) => item.matchInfo?.matched) || []
|
||||
expect(matchingSubcomponents.length).toBe(1)
|
||||
expect(matchingSubcomponents[0].metadata?.name).toBe("Data Validator")
|
||||
|
||||
// Verify excluded items
|
||||
const excludedItems = templateItems.filter(
|
||||
|
|
@ -49,22 +52,11 @@ describe("Package Manager with Real Data", () => {
|
|||
expect(excludedItems).toContainEqual(expect.objectContaining({ name: "Data Engineer" }))
|
||||
expect(excludedItems).toContainEqual(expect.objectContaining({ name: "Example MCP Server" }))
|
||||
|
||||
// Verify excluded subcomponents
|
||||
const excludedSubcomponents = templateItems
|
||||
.find((item) => item.name === "Data Platform Package")
|
||||
?.items?.filter(
|
||||
(subItem) =>
|
||||
!filteredItems[0].items?.some(
|
||||
(filtered) => filtered.metadata?.name === subItem.metadata?.name,
|
||||
),
|
||||
)
|
||||
expect(excludedSubcomponents).toContainEqual(
|
||||
expect.objectContaining({
|
||||
metadata: expect.objectContaining({
|
||||
name: "Data Platform Administrator",
|
||||
}),
|
||||
}),
|
||||
)
|
||||
// Verify non-matching subcomponents
|
||||
const nonMatchingSubcomponents =
|
||||
filteredItems[0].items?.filter((item) => !item.matchInfo?.matched) || []
|
||||
expect(nonMatchingSubcomponents.length).toBe(1)
|
||||
expect(nonMatchingSubcomponents[0].metadata?.name).toBe("Data Platform Administrator")
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -114,8 +106,11 @@ describe("Package Manager with Real Data", () => {
|
|||
const packageWithServer = filteredItems.find((item) => item.type === "package")
|
||||
expect(packageWithServer).toBeDefined()
|
||||
expect(packageWithServer?.name).toBe("Data Platform Package")
|
||||
expect(packageWithServer?.items?.length).toBe(1)
|
||||
expect(packageWithServer?.items?.[0].metadata?.name).toBe("Data Validator")
|
||||
|
||||
// Count how many subcomponents have matchInfo.matched = true
|
||||
const matchingSubcomponents = packageWithServer?.items?.filter((item) => item.matchInfo?.matched) || []
|
||||
expect(matchingSubcomponents.length).toBe(1)
|
||||
expect(matchingSubcomponents[0].metadata?.name).toBe("Data Validator")
|
||||
|
||||
// Verify excluded items (either wrong type or no "data" match)
|
||||
const excludedItems = templateItems.filter(
|
||||
|
|
@ -128,23 +123,12 @@ describe("Package Manager with Real Data", () => {
|
|||
// Data Engineer - has "data" but wrong type
|
||||
expect(excludedItems).toContainEqual(expect.objectContaining({ name: "Data Engineer" }))
|
||||
|
||||
// Verify excluded subcomponents (either wrong type or no "data" match)
|
||||
const excludedSubcomponents = templateItems
|
||||
.find((item) => item.name === "Data Platform Package")
|
||||
?.items?.filter(
|
||||
(subItem) =>
|
||||
!filteredItems
|
||||
.find((item) => item.name === "Data Platform Package")
|
||||
?.items?.some((filtered) => filtered.metadata?.name === subItem.metadata?.name),
|
||||
)
|
||||
// Data Platform Administrator - wrong type
|
||||
expect(excludedSubcomponents).toContainEqual(
|
||||
expect.objectContaining({
|
||||
metadata: expect.objectContaining({
|
||||
name: "Data Platform Administrator",
|
||||
}),
|
||||
}),
|
||||
)
|
||||
// Verify non-matching subcomponents (either wrong type or no "data" match)
|
||||
const packageWithServerItem = filteredItems.find((item) => item.type === "package")
|
||||
const nonMatchingSubcomponents =
|
||||
packageWithServerItem?.items?.filter((item) => !item.matchInfo?.matched) || []
|
||||
expect(nonMatchingSubcomponents.length).toBe(1)
|
||||
expect(nonMatchingSubcomponents[0].metadata?.name).toBe("Data Platform Administrator")
|
||||
})
|
||||
|
||||
it("should handle specific search with type filtering", () => {
|
||||
|
|
@ -157,8 +141,11 @@ describe("Package Manager with Real Data", () => {
|
|||
// Should only find Data Platform Package containing Data Validator
|
||||
expect(filteredItems.length).toBe(1)
|
||||
expect(filteredItems[0].name).toBe("Data Platform Package")
|
||||
expect(filteredItems[0].items?.length).toBe(1)
|
||||
expect(filteredItems[0].items?.[0].metadata?.name).toBe("Data Validator")
|
||||
|
||||
// Count how many subcomponents have matchInfo.matched = true
|
||||
const matchingSubcomponents = filteredItems[0].items?.filter((item) => item.matchInfo?.matched) || []
|
||||
expect(matchingSubcomponents.length).toBe(1)
|
||||
expect(matchingSubcomponents[0].metadata?.name).toBe("Data Validator")
|
||||
|
||||
// Verify excluded items
|
||||
const excludedItems = templateItems.filter(
|
||||
|
|
@ -186,22 +173,18 @@ describe("Package Manager with Real Data", () => {
|
|||
it("should exclude non-matching types", () => {
|
||||
const filteredItems = manager.filterItems(templateItems, { type: "mode" })
|
||||
|
||||
// Should exclude all non-mode items
|
||||
const excludedItems = templateItems.filter(
|
||||
(item) => !filteredItems.some((filtered) => filtered.name === item.name),
|
||||
)
|
||||
expect(excludedItems).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: "Data Processor",
|
||||
type: "mcp server",
|
||||
}),
|
||||
)
|
||||
expect(excludedItems).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: "Data Platform Package",
|
||||
type: "package",
|
||||
}),
|
||||
)
|
||||
// Should include only mode items
|
||||
const modeItems = filteredItems.filter((item) => item.type === "mode")
|
||||
expect(modeItems.length).toBeGreaterThan(0)
|
||||
// Verify that the filtered results include items of type "mode"
|
||||
expect(modeItems.length).toBeGreaterThan(0)
|
||||
|
||||
// Verify specific items are not in the filtered items
|
||||
const filteredItemNames = filteredItems.map((item) => item.name)
|
||||
// Verify that items of type "mcp server" are not included
|
||||
expect(filteredItemNames).not.toContain("Data Processor")
|
||||
expect(filteredItemNames).not.toContain("Example MCP Server")
|
||||
expect(filteredItemNames).not.toContain("File Analyzer MCP Server")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ interface ExpandableSectionProps {
|
|||
children: React.ReactNode
|
||||
className?: string
|
||||
defaultExpanded?: boolean
|
||||
badge?: string
|
||||
}
|
||||
|
||||
export const ExpandableSection: React.FC<ExpandableSectionProps> = ({
|
||||
|
|
@ -13,6 +14,7 @@ export const ExpandableSection: React.FC<ExpandableSectionProps> = ({
|
|||
children,
|
||||
className,
|
||||
defaultExpanded = false,
|
||||
badge,
|
||||
}) => {
|
||||
const [isExpanded, setIsExpanded] = useState(defaultExpanded)
|
||||
|
||||
|
|
@ -23,14 +25,24 @@ export const ExpandableSection: React.FC<ExpandableSectionProps> = ({
|
|||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
aria-expanded={isExpanded}
|
||||
aria-controls="details-content">
|
||||
<span className="font-medium">{title}</span>
|
||||
<span
|
||||
className={cn(
|
||||
"codicon",
|
||||
isExpanded ? "codicon-chevron-down" : "codicon-chevron-right",
|
||||
"transition-transform duration-200",
|
||||
<span className="font-medium flex items-center">
|
||||
<span className="codicon codicon-list-unordered mr-1"></span>
|
||||
{title}
|
||||
</span>
|
||||
<div className="flex items-center">
|
||||
{badge && (
|
||||
<span className="mr-2 text-xs bg-vscode-badge-background text-vscode-badge-foreground px-1 py-0.5 rounded">
|
||||
{badge}
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
<span
|
||||
className={cn(
|
||||
"codicon",
|
||||
isExpanded ? "codicon-chevron-down" : "codicon-chevron-right",
|
||||
"transition-transform duration-200",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
id="details-content"
|
||||
|
|
@ -40,7 +52,7 @@ export const ExpandableSection: React.FC<ExpandableSectionProps> = ({
|
|||
)}
|
||||
role="region"
|
||||
aria-labelledby="details-button">
|
||||
<div className="py-2">{children}</div>
|
||||
<div className="py-2 px-1 bg-vscode-panel-background rounded-sm">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -150,10 +150,43 @@ export const PackageManagerItemCard: React.FC<PackageManagerItemCardProps> = ({
|
|||
</div>
|
||||
|
||||
{groupedItems && (
|
||||
<ExpandableSection title="Details">
|
||||
{Object.entries(groupedItems).map(([type, group]) => (
|
||||
<TypeGroup key={type} type={type} items={group.items} />
|
||||
))}
|
||||
<ExpandableSection
|
||||
title="Component Details"
|
||||
badge={
|
||||
filters.search
|
||||
? (() => {
|
||||
const matchCount =
|
||||
item.items?.filter(
|
||||
(subItem) =>
|
||||
(subItem.metadata?.name || "")
|
||||
.toLowerCase()
|
||||
.includes(filters.search.toLowerCase()) ||
|
||||
(subItem.metadata?.description || "")
|
||||
.toLowerCase()
|
||||
.includes(filters.search.toLowerCase()),
|
||||
).length || 0
|
||||
return matchCount > 0
|
||||
? `${matchCount} match${matchCount !== 1 ? "es" : ""}`
|
||||
: undefined
|
||||
})()
|
||||
: undefined
|
||||
}
|
||||
defaultExpanded={
|
||||
!!filters.search &&
|
||||
(item.items?.some(
|
||||
(subItem) =>
|
||||
(subItem.metadata?.name || "").toLowerCase().includes(filters.search.toLowerCase()) ||
|
||||
(subItem.metadata?.description || "")
|
||||
.toLowerCase()
|
||||
.includes(filters.search.toLowerCase()),
|
||||
) ||
|
||||
false)
|
||||
}>
|
||||
<div className="space-y-4">
|
||||
{Object.entries(groupedItems).map(([type, group]) => (
|
||||
<TypeGroup key={type} type={type} items={group.items} searchTerm={filters.search} />
|
||||
))}
|
||||
</div>
|
||||
</ExpandableSection>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { formatItemText } from "../utils/grouping"
|
||||
|
||||
interface TypeGroupProps {
|
||||
type: string
|
||||
|
|
@ -11,9 +10,10 @@ interface TypeGroupProps {
|
|||
path?: string
|
||||
}>
|
||||
className?: string
|
||||
searchTerm?: string
|
||||
}
|
||||
|
||||
export const TypeGroup: React.FC<TypeGroupProps> = ({ type, items, className }) => {
|
||||
export const TypeGroup: React.FC<TypeGroupProps> = ({ type, items, className, searchTerm }) => {
|
||||
const getTypeLabel = (type: string) => {
|
||||
switch (type) {
|
||||
case "mode":
|
||||
|
|
@ -33,15 +33,41 @@ export const TypeGroup: React.FC<TypeGroupProps> = ({ type, items, className })
|
|||
return null
|
||||
}
|
||||
|
||||
// Check if an item matches the search term
|
||||
const itemMatchesSearch = (item: { name: string; description?: string }) => {
|
||||
if (!searchTerm) return false
|
||||
const term = searchTerm.toLowerCase()
|
||||
return item.name.toLowerCase().includes(term) || (item.description || "").toLowerCase().includes(term)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("mb-4", className)}>
|
||||
<h4 className="text-sm font-medium text-vscode-foreground mb-2">{getTypeLabel(type)}</h4>
|
||||
<ol className="list-decimal list-inside space-y-1">
|
||||
{items.map((item, index) => (
|
||||
<li key={`${item.path || index}`} className="text-sm text-vscode-foreground pl-1" title={item.path}>
|
||||
<span className="text-vscode-descriptionForeground">{formatItemText(item)}</span>
|
||||
</li>
|
||||
))}
|
||||
{items.map((item, index) => {
|
||||
const matches = itemMatchesSearch(item)
|
||||
return (
|
||||
<li
|
||||
key={`${item.path || index}`}
|
||||
className={cn(
|
||||
"text-sm pl-1",
|
||||
matches ? "text-vscode-foreground font-medium" : "text-vscode-foreground",
|
||||
)}
|
||||
title={item.path}>
|
||||
<span className={cn("font-medium", matches ? "text-vscode-textLink" : "")}>
|
||||
{item.name}
|
||||
</span>
|
||||
{item.description && (
|
||||
<span className="text-vscode-descriptionForeground"> - {item.description}</span>
|
||||
)}
|
||||
{matches && (
|
||||
<span className="ml-2 text-xs bg-vscode-badge-background text-vscode-badge-foreground px-1 py-0.5 rounded">
|
||||
match
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ol>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -29,12 +29,23 @@ export const filterItems = (items: PackageManagerItem[], filters: Filters): Pack
|
|||
// Search filter
|
||||
if (filters.search) {
|
||||
const searchTerm = filters.search.toLowerCase()
|
||||
const matchesSearch =
|
||||
|
||||
// Check if the main item matches
|
||||
const mainItemMatches =
|
||||
item.name.toLowerCase().includes(searchTerm) ||
|
||||
(item.description || "").toLowerCase().includes(searchTerm) ||
|
||||
(item.author || "").toLowerCase().includes(searchTerm)
|
||||
|
||||
if (!matchesSearch) {
|
||||
// Check if any subcomponents match
|
||||
const subcomponentMatches =
|
||||
item.items?.some(
|
||||
(subItem) =>
|
||||
(subItem.metadata?.name || "").toLowerCase().includes(searchTerm) ||
|
||||
(subItem.metadata?.description || "").toLowerCase().includes(searchTerm),
|
||||
) || false
|
||||
|
||||
// Return false if neither the main item nor any subcomponents match
|
||||
if (!mainItemMatches && !subcomponentMatches) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,15 @@ export function formatItemText(item: { name: string; description?: string }): st
|
|||
if (!item.description) {
|
||||
return item.name
|
||||
}
|
||||
return `${item.name} - ${item.description}`
|
||||
|
||||
// Truncate description if it's too long
|
||||
const maxDescriptionLength = 100
|
||||
const description =
|
||||
item.description.length > maxDescriptionLength
|
||||
? `${item.description.substring(0, maxDescriptionLength)}...`
|
||||
: item.description
|
||||
|
||||
return `${item.name} - ${description}`
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue