Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit3e340bf

Browse files
committed
feat: optimize workspace search with performance improvements
- Pre-compile regex patterns to avoid repeated compilation- Cache stringified metadata to reduce JSON serialization overhead- Extract input field processing into reusable helper method- Add input validation to prevent performance issues from long search terms- Add comprehensive error handling for edge cases and malformed data
1 parentbab6c21 commit3e340bf

File tree

1 file changed

+139
-43
lines changed

1 file changed

+139
-43
lines changed

‎src/workspacesProvider.ts‎

Lines changed: 139 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export class WorkspaceProvider
4343
privatefetching=false;
4444
privatevisible=false;
4545
privatesearchFilter="";
46+
privatemetadataCache:Record<string,string>={};
4647

4748
constructor(
4849
privatereadonlygetWorkspacesQuery:WorkspaceQuery,
@@ -54,6 +55,10 @@ export class WorkspaceProvider
5455
}
5556

5657
setSearchFilter(filter:string){
58+
// Validate search term length to prevent performance issues
59+
if(filter.length>200){
60+
filter=filter.substring(0,200);
61+
}
5762
this.searchFilter=filter;
5863
this.refresh(undefined);
5964
}
@@ -73,6 +78,9 @@ export class WorkspaceProvider
7378
}
7479
this.fetching=true;
7580

81+
// Clear metadata cache when refreshing to ensure data consistency
82+
this.clearMetadataCache();
83+
7684
// It is possible we called fetchAndRefresh() manually (through the button
7785
// for example), in which case we might still have a pending refresh that
7886
// needs to be cleared.
@@ -325,76 +333,164 @@ export class WorkspaceProvider
325333
}
326334

327335
/**
328-
*Check if a workspace matches the given search term using smart search logic.
329-
*Prioritizesexact wordmatches oversubstringmatches.
336+
*Extract and normalize searchable text fields from a workspace.
337+
*This helper method reduces code duplication betweenexact wordandsubstringmatching.
330338
*/
331-
privatematchesSearchTerm(
332-
workspace:WorkspaceTreeItem,
333-
searchTerm:string,
334-
):boolean{
335-
constworkspaceName=workspace.workspace.name.toLowerCase();
336-
constownerName=workspace.workspace.owner_name.toLowerCase();
339+
privateextractSearchableFields(workspace:WorkspaceTreeItem):{
340+
workspaceName:string;
341+
ownerName:string;
342+
templateName:string;
343+
status:string;
344+
agentNames:string[];
345+
agentMetadataText:string;
346+
}{
347+
// Handle null/undefined workspace data safely
348+
constworkspaceName=(workspace.workspace.name||"").toLowerCase();
349+
constownerName=(workspace.workspace.owner_name||"").toLowerCase();
337350
consttemplateName=(
338351
workspace.workspace.template_display_name||
339352
workspace.workspace.template_name||
340353
""
341354
).toLowerCase();
342-
conststatus=workspace.workspace.latest_build.status.toLowerCase();
355+
conststatus=(
356+
workspace.workspace.latest_build?.status||""
357+
).toLowerCase();
343358

344-
// Check if any agent names match the search term
345-
constagents=extractAgents(workspace.workspace.latest_build.resources);
346-
constagentNames=agents.map((agent)=>agent.name.toLowerCase());
347-
consthasMatchingAgent=agentNames.some((agentName)=>
348-
agentName.includes(searchTerm),
359+
// Extract agent names with null safety
360+
constagents=extractAgents(
361+
workspace.workspace.latest_build?.resources||[],
349362
);
363+
constagentNames=agents
364+
.map((agent)=>(agent.name||"").toLowerCase())
365+
.filter((name)=>name.length>0);
350366

351-
// Check if any agent metadata contains the search term
352-
consthasMatchingMetadata=agents.some((agent)=>{
353-
constwatcher=this.agentWatchers[agent.id];
354-
if(watcher?.metadata){
355-
returnwatcher.metadata.some((metadata)=>{
356-
constmetadataStr=JSON.stringify(metadata).toLowerCase();
357-
returnmetadataStr.includes(searchTerm);
358-
});
359-
}
360-
returnfalse;
361-
});
367+
// Extract and cache agent metadata with error handling
368+
letagentMetadataText="";
369+
constmetadataCacheKey=agents.map((agent)=>agent.id).join(",");
362370

363-
// Smart search: Try exact word match first, then fall back to substring
364-
constsearchWords=searchTerm
365-
.split(/\s+/)
366-
.filter((word)=>word.length>0);
367-
constallText=[
371+
if(this.metadataCache[metadataCacheKey]){
372+
agentMetadataText=this.metadataCache[metadataCacheKey];
373+
}else{
374+
constmetadataStrings:string[]=[];
375+
agents.forEach((agent)=>{
376+
constwatcher=this.agentWatchers[agent.id];
377+
if(watcher?.metadata){
378+
watcher.metadata.forEach((metadata)=>{
379+
try{
380+
metadataStrings.push(JSON.stringify(metadata).toLowerCase());
381+
}catch(error){
382+
// Handle JSON serialization errors gracefully
383+
this.storage.output.warn(
384+
`Failed to serialize metadata for agent${agent.id}:${error}`,
385+
);
386+
}
387+
});
388+
}
389+
});
390+
agentMetadataText=metadataStrings.join(" ");
391+
this.metadataCache[metadataCacheKey]=agentMetadataText;
392+
}
393+
394+
return{
368395
workspaceName,
369396
ownerName,
370397
templateName,
371398
status,
372-
...agentNames,
399+
agentNames,
400+
agentMetadataText,
401+
};
402+
}
403+
404+
/**
405+
* Check if a workspace matches the given search term using smart search logic.
406+
* Prioritizes exact word matches over substring matches.
407+
*/
408+
privatematchesSearchTerm(
409+
workspace:WorkspaceTreeItem,
410+
searchTerm:string,
411+
):boolean{
412+
// Early return for empty search terms
413+
if(!searchTerm||searchTerm.trim().length===0){
414+
returntrue;
415+
}
416+
417+
// Extract all searchable fields once
418+
constfields=this.extractSearchableFields(workspace);
419+
420+
// Pre-compile regex patterns for exact word matching
421+
constsearchWords=searchTerm
422+
.split(/\s+/)
423+
.filter((word)=>word.length>0);
424+
425+
constregexPatterns:RegExp[]=[];
426+
for(constwordofsearchWords){
427+
try{
428+
// Escape special regex characters to prevent injection
429+
constescapedWord=word.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");
430+
regexPatterns.push(newRegExp(`\\b${escapedWord}\\b`,"i"));
431+
}catch(error){
432+
// Handle invalid regex patterns
433+
this.storage.output.warn(
434+
`Invalid regex pattern for search word "${word}":${error}`,
435+
);
436+
// Fall back to simple substring matching for this word
437+
continue;
438+
}
439+
}
440+
441+
// Combine all text for exact word matching
442+
constallText=[
443+
fields.workspaceName,
444+
fields.ownerName,
445+
fields.templateName,
446+
fields.status,
447+
...fields.agentNames,
448+
fields.agentMetadataText,
373449
].join(" ");
374450

375451
// Check for exact word matches (higher priority)
376452
consthasExactWordMatch=
377-
searchWords.length>0&&
378-
searchWords.some((word)=>{
379-
// Escape special regex characters to prevent injection
380-
constescapedWord=word.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");
381-
constwordBoundaryRegex=newRegExp(`\\b${escapedWord}\\b`,"i");
382-
returnwordBoundaryRegex.test(allText);
453+
regexPatterns.length>0&&
454+
regexPatterns.some((pattern)=>{
455+
try{
456+
returnpattern.test(allText);
457+
}catch(error){
458+
// Handle regex test errors gracefully
459+
this.storage.output.warn(
460+
`Regex test failed for pattern${pattern}:${error}`,
461+
);
462+
returnfalse;
463+
}
383464
});
384465

385466
// Check for substring matches (lower priority) - only if no exact word match
386467
consthasSubstringMatch=
387468
!hasExactWordMatch&&
388-
(workspaceName.includes(searchTerm)||
389-
ownerName.includes(searchTerm)||
390-
templateName.includes(searchTerm)||
391-
status.includes(searchTerm)||
392-
hasMatchingAgent||
393-
hasMatchingMetadata);
469+
(fields.workspaceName.includes(searchTerm)||
470+
fields.ownerName.includes(searchTerm)||
471+
fields.templateName.includes(searchTerm)||
472+
fields.status.includes(searchTerm)||
473+
fields.agentNames.some((agentName)=>agentName.includes(searchTerm))||
474+
fields.agentMetadataText.includes(searchTerm));
394475

395476
// Return true if either exact word match or substring match
396477
returnhasExactWordMatch||hasSubstringMatch;
397478
}
479+
480+
/**
481+
* Clear the metadata cache when workspaces are refreshed to ensure data consistency.
482+
* Also clears cache if it grows too large to prevent memory issues.
483+
*/
484+
privateclearMetadataCache():void{
485+
// Clear cache if it grows too large (prevent memory issues)
486+
constcacheSize=Object.keys(this.metadataCache).length;
487+
if(cacheSize>1000){
488+
this.storage.output.info(
489+
`Clearing metadata cache due to size (${cacheSize} entries)`,
490+
);
491+
}
492+
this.metadataCache={};
493+
}
398494
}
399495

400496
/**

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp