Skip to content

Commit ce65cad

Browse files
committed
fix: uri, hash, index move
1 parent bb50bb6 commit ce65cad

4 files changed

Lines changed: 89 additions & 38 deletions

File tree

src/__tests__/__snapshots__/patternFly.getResources.test.ts.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ exports[`getPatternFlyMcpResources should return multiple organized facets: prop
2828
"keywordsIndex",
2929
"keywordsMap",
3030
"pathIndex",
31+
"uriIndex",
32+
"hashIndex",
3133
"byPath",
3234
"byUri",
3335
"byVersion",

src/patternFly.getResources.ts

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,9 @@ type PatternFlyMcpResourceMetadata = {
169169
* @property keywordsIndex - Patternfly available keywords index.
170170
* @property keywordsMap - Patternfly available keywords by resource name then by version.
171171
* @property isFallbackDocumentation - Whether the fallback documentation is used.
172-
* @property pathIndex - Patternfly documentation path index.
172+
* @property pathIndex - Patternfly documentation path->name map for helping refine search results.
173+
* @property uriIndex - Patternfly documentation uri->name map for helping refine search results.
174+
* @property hashIndex - Patternfly documentation hash->name map for helping refine search results.
173175
* @property byPath - Patternfly documentation by path with entries
174176
* @property byUri - Patternfly documentation by uri with entries
175177
* @property byVersion - Patternfly documentation by version with entries
@@ -182,7 +184,9 @@ interface PatternFlyMcpAvailableResources extends PatternFlyVersionContext {
182184
keywordsIndex: string[];
183185
keywordsMap: PatternFlyMcpKeywordsMap;
184186
isFallbackDocumentation: boolean;
185-
pathIndex: string[];
187+
pathIndex: Map<string, string>;
188+
uriIndex: Map<string, string>;
189+
hashIndex: Map<string, string>;
186190
byPath: PatternFlyMcpResourcesByPath;
187191
byUri: PatternFlyMcpResourcesByUri;
188192
byVersion: PatternFlyMcpResourcesByVersion;
@@ -467,7 +471,9 @@ const getPatternFlyMcpResources = async (contextPathOverride?: string): Promise<
467471
const byPath: PatternFlyMcpResourcesByPath = {};
468472
const byUri: PatternFlyMcpResourcesByUri = {};
469473
const byVersion: PatternFlyMcpResourcesByVersion = {};
470-
const pathIndex = new Set<string>();
474+
const pathIndexMap = new Map<string, string>();
475+
const uriIndexMap = new Map<string, string>();
476+
const hashIndexMap = new Map<string, string>();
471477
const rawKeywordsMap: PatternFlyMcpKeywordsMap = new Map();
472478

473479
const catalog = [...Object.entries(originalDocs.docs), ...Array.from(componentNamesByDocs)];
@@ -476,6 +482,8 @@ const getPatternFlyMcpResources = async (contextPathOverride?: string): Promise<
476482
const name = unifiedName.toLowerCase();
477483
const groupId = generateHash(name);
478484

485+
hashIndexMap.set(groupId, name);
486+
479487
if (!resources.has(name)) {
480488
// Include search and filter contextual `undefined` metadata for each resource.
481489
resources.set(name, {
@@ -500,8 +508,12 @@ const getPatternFlyMcpResources = async (contextPathOverride?: string): Promise<
500508
const uri = `patternfly://docs/${encodeURIComponent(name)}${buildSearchString({ version }, { prefix: true })}`;
501509
const uriId = `patternfly://docs/${encodeURIComponent(id)}`;
502510

511+
hashIndexMap.set(id, name);
512+
uriIndexMap.set(uri, name);
513+
uriIndexMap.set(uriId, name);
514+
503515
if (path) {
504-
pathIndex.add(path);
516+
pathIndexMap.set(path, name);
505517
}
506518

507519
resource.versions[version] ??= {
@@ -524,6 +536,9 @@ const getPatternFlyMcpResources = async (contextPathOverride?: string): Promise<
524536

525537
resource.versions[version].uriSchemas = uriSchemas;
526538
resource.versions[version].uriSchemasId = uriSchemasId;
539+
540+
uriIndexMap.set(uriSchemas, name);
541+
uriIndexMap.set(uriSchemasId, name);
527542
}
528543

529544
const extendedEntry = {
@@ -555,7 +570,6 @@ const getPatternFlyMcpResources = async (contextPathOverride?: string): Promise<
555570
byVersion[version]?.push(extendedEntry);
556571

557572
mutateKeyWordsMap(rawKeywordsMap, { keyword: name, name, version });
558-
mutateKeyWordsMap(rawKeywordsMap, { keyword: groupId, name, version });
559573

560574
if (entry.displayName) {
561575
mutateKeyWordsMap(rawKeywordsMap, { keyword: entry.displayName, name, version });
@@ -573,26 +587,6 @@ const getPatternFlyMcpResources = async (contextPathOverride?: string): Promise<
573587
mutateKeyWordsMap(rawKeywordsMap, { keyword: entry.description, name, version });
574588
}
575589

576-
if (id) {
577-
mutateKeyWordsMap(rawKeywordsMap, { keyword: id, name, version });
578-
}
579-
580-
if (uri) {
581-
mutateKeyWordsMap(rawKeywordsMap, { keyword: uri, name, version });
582-
}
583-
584-
if (uriId) {
585-
mutateKeyWordsMap(rawKeywordsMap, { keyword: uriId, name, version });
586-
}
587-
588-
if (uriSchemas) {
589-
mutateKeyWordsMap(rawKeywordsMap, { keyword: uriSchemas, name, version });
590-
}
591-
592-
if (uriSchemasId) {
593-
mutateKeyWordsMap(rawKeywordsMap, { keyword: uriSchemasId, name, version });
594-
}
595-
596590
resource.entries.push(extendedEntry);
597591
resource.versions[version].entries.push(extendedEntry);
598592
});
@@ -615,7 +609,9 @@ const getPatternFlyMcpResources = async (contextPathOverride?: string): Promise<
615609
...Array.from(filteredKeywords.keys())
616610
])).sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' })),
617611
keywordsMap: filteredKeywords,
618-
pathIndex: Array.from(pathIndex).sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' })),
612+
pathIndex: pathIndexMap,
613+
uriIndex: uriIndexMap,
614+
hashIndex: hashIndexMap,
619615
byPath,
620616
byUri,
621617
byVersion,

src/patternFly.search.ts

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
fuzzySearch,
33
type FuzzySearch,
4+
type FuzzySearchOptions,
45
type FuzzySearchResult
56
} from './server.search';
67
import { memo } from './server.caching';
@@ -11,7 +12,6 @@ import {
1112
type PatternFlyMcpDocsMeta,
1213
type PatternFlyMcpResourceMetadata
1314
} from './patternFly.getResources';
14-
import { parsePatternFlyUri } from './patternFly.helpers';
1515
import { type PatternFlyMcpDocsCatalogDoc } from './docs.embedded';
1616

1717
/**
@@ -31,13 +31,15 @@ type PatternFlyMcpResourceFilteredMetadata = Omit<PatternFlyMcpResourceMetadata,
3131
* @property [version] - PatternFly version to filter search results. Defaults to undefined for all versions.
3232
* @property [category] - Category to filter search results. Defaults to undefined for all categories.
3333
* @property [section] - Section to filter search results. Defaults to undefined for all sections.
34-
* @property [name] - Name to filter search results. Defaults to undefined for all names.
34+
* @property [name] - Name, or hash id, to filter search results. Defaults to undefined for all names and IDs.
35+
* @property [path] - Document path, or URI, to filter search results. Defaults to undefined for all paths and URIs.
3536
*/
3637
interface FilterPatternFlyFilters {
3738
version?: string | undefined;
3839
category?: string | undefined;
3940
section?: string | undefined;
4041
name?: string | undefined;
42+
path?: string | undefined;
4143
}
4244

4345
/**
@@ -110,6 +112,17 @@ interface SearchPatternFlyOptions {
110112
maxResults?: number;
111113
}
112114

115+
/**
116+
* A list of prioritized filters used for matching and processing
117+
* filter patterns. Each filter corresponds to a specific key in the
118+
* FilterPatternFlyFilters type.
119+
*
120+
* @note **Sequence matters** - This array defines the order of priority for
121+
* filter keys, which may be used in operations such as sorting or
122+
* applying specific filtering logic. See {@link FilterPatternFlyFilters}
123+
*/
124+
const PRIORITY_FILTERS: (keyof FilterPatternFlyFilters)[] = ['name', 'section', 'category', 'version', 'path'];
125+
113126
/**
114127
* Apply sequenced priority filters for predictable filtering, filter PatternFly data.
115128
*
@@ -169,13 +182,16 @@ const filterPatternFly = async (
169182
const matchesVersion = !updatedFilters.version || String(entry.version).toLowerCase() === updatedFilters.version;
170183
const matchesCategory = !updatedFilters.category || filterMatch(entry.category, updatedFilters.category);
171184
const matchesSection = !updatedFilters.section || filterMatch(entry.section, updatedFilters.section);
185+
const matchesPath = !updatedFilters.path || filterMatch(entry.path, updatedFilters.path) ||
186+
filterMatch(entry.uriId, updatedFilters.path) || filterMatch(entry.uriSchemas, updatedFilters.path) ||
187+
filterMatch(entry.uriSchemasId, updatedFilters.path) || filterMatch(entry.uri, updatedFilters.path);
172188

173189
// Filter order matters specific id -> group id -> group name
174190
const matchesName = !updatedFilters.name || filterMatch(entry.id, updatedFilters.name) ||
175191
filterMatch(entry.groupId, updatedFilters.name) || filterMatch(entry.name, updatedFilters.name);
176192

177193
// Any missing filter registers as true. Only filters that are active run their check.
178-
return matchesVersion && matchesCategory && matchesSection && matchesName;
194+
return matchesVersion && matchesCategory && matchesSection && matchesPath && matchesName;
179195
});
180196

181197
if (matchedEntries.length > 0) {
@@ -221,7 +237,7 @@ filterPatternFly.memo = memo(filterPatternFly, DEFAULT_OPTIONS.resourceMemoOptio
221237
* @param filters
222238
* @param mcpResources
223239
* @param [options] - Optional settings object
224-
* @param [options.prioritizedFilters] - Array of filters to prioritize. Defaults to `['name', 'section', 'category', 'version']`.
240+
* @param [options.prioritizedFilters] - Array of filters to prioritize. Defaults to {@link PRIORITY_FILTERS}.
225241
* @param [options.maxResultsLimit] - Max number of results internal conditions need to match before they return the original result. Defaults to `1`.
226242
* @param [options.useExistingFilters] - Use the existing filters or bypass them. Defaults to `true`.
227243
* @returns {Promise<FilterPatternFlyResults>} - A Promise resolving to the filtering results.
@@ -230,7 +246,7 @@ const dynamicFilterPatternFly = async (
230246
searchQuery: string, filters: FilterPatternFlyFilters | undefined,
231247
mcpResources?: Promise<PatternFlyMcpAvailableResources> | Map<string, PatternFlyMcpResourceFilteredMetadata>,
232248
{
233-
prioritizedFilters = ['name', 'section', 'category', 'version'],
249+
prioritizedFilters = PRIORITY_FILTERS,
234250
maxResultsLimit = 1,
235251
useExistingFilters = true
236252
}: { prioritizedFilters?: (keyof FilterPatternFlyFilters)[]; maxResultsLimit?: number; useExistingFilters?: boolean } = {}
@@ -311,26 +327,31 @@ const searchPatternFly = async (searchQuery: unknown, filters?: FilterPatternFly
311327
const updatedFilters = filters || {};
312328
const isWildCardAll = coercedSearchQuery === '*' || coercedSearchQuery.toLowerCase() === 'all' || coercedSearchQuery === '';
313329
const isSearchWildCardAll = allowWildCardAll && isWildCardAll;
330+
const pathMatchName = updatedResources.pathIndex?.get(coercedSearchQuery);
331+
const uriMatchName = updatedResources.uriIndex?.get(coercedSearchQuery);
332+
const hashMatchName = updatedResources.hashIndex?.get(coercedSearchQuery);
314333
let search: FuzzySearch | undefined;
315334
let searchResults: FuzzySearchResult[] = [];
316335

317336
// Perform wildcard all search or fuzzy search
318337
if (isSearchWildCardAll) {
319338
searchResults = updatedResources.keywordsIndex.map(name => ({ matchType: 'all', distance: 0, item: name } as FuzzySearchResult));
339+
} else if (pathMatchName || uriMatchName || hashMatchName) {
340+
searchResults = [
341+
{
342+
matchType: 'exact',
343+
distance: 0,
344+
item: pathMatchName || uriMatchName || hashMatchName
345+
} as FuzzySearchResult
346+
];
320347
} else {
321-
const patternflyUri = parsePatternFlyUri.memo(coercedSearchQuery);
322-
const fuzzySearchSettings = {
348+
const fuzzySearchSettings: FuzzySearchOptions = {
323349
maxDistance,
324350
maxResults,
325351
isFuzzyMatch: true,
326352
deduplicateByNormalized: true
327353
};
328354

329-
if (patternflyUri) {
330-
fuzzySearchSettings.maxDistance = 1;
331-
fuzzySearchSettings.isFuzzyMatch = false;
332-
}
333-
334355
// Pass the original searchQuery, fuzzySearch has its own normalization.
335356
search = fuzzySearch(searchQuery, updatedResources.keywordsIndex, fuzzySearchSettings);
336357
searchResults = search.results;

tests/e2e/stdioTransport.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,38 @@ describe('Builtin tools, STDIO', () => {
209209
'No PatternFly resources found matching "lorem ipsum dolor sit amet"',
210210
'Use a search all'
211211
]
212+
},
213+
{
214+
description: 'hash search query',
215+
searchQuery: '19b2a9418c744e70da9e3dd0965d1948ec1ebbe4',
216+
contains: [
217+
'Showing 1 exact match',
218+
'**button**'
219+
]
220+
},
221+
{
222+
description: 'partial hash search query',
223+
searchQuery: '19b2a',
224+
contains: [
225+
'No PatternFly resources found matching "19b2a"',
226+
'Use a search all'
227+
]
228+
},
229+
{
230+
description: 'uri search query',
231+
searchQuery: 'patternfly://docs/19b2a9418c744e70da9e3dd0965d1948ec1ebbe4',
232+
contains: [
233+
'Showing 1 exact match',
234+
'**button**'
235+
]
236+
},
237+
{
238+
description: 'partial uri search query',
239+
searchQuery: 'patternfly://docs/19b2a94',
240+
contains: [
241+
'No PatternFly resources found matching "patternfly://docs/19b2a94"',
242+
'Use a search all'
243+
]
212244
}
213245
])('should perform searchPatternFlyDocs: $description', async ({ searchQuery, version, contains }) => {
214246
const req = {

0 commit comments

Comments
 (0)