@@ -73,6 +73,9 @@ const SearchBar: React.FC<SearchBarProps> = ({
7373 const [ metadataCategoryValue , setMetadataCategoryValue ] = useState ( "" ) ;
7474 const [ metadataValue , setMetadataValue ] = useState ( "" ) ;
7575 const [ logSearchValue , setLogSearchValue ] = useState ( "" ) ;
76+ const [ editingCriterionId , setEditingCriterionId ] = useState < string | null > (
77+ null ,
78+ ) ;
7679 const searchBarRef = useRef < HTMLDivElement > ( null ) ;
7780
7881 // Memoize extracted search terms to prevent unnecessary re-renders
@@ -106,11 +109,20 @@ const SearchBar: React.FC<SearchBarProps> = ({
106109 // Click-outside detection to collapse search
107110 useEffect ( ( ) => {
108111 const handleClickOutside = ( event : MouseEvent ) => {
109- if (
110- searchBarRef . current &&
111- ! searchBarRef . current . contains ( event . target as Node ) &&
112- isSearchExpanded
113- ) {
112+ const target = event . target as Node ;
113+
114+ // Check if click is inside the search bar
115+ if ( searchBarRef . current && searchBarRef . current . contains ( target ) ) {
116+ return ;
117+ }
118+
119+ // Check if click is inside a dropdown menu portal
120+ // Radix UI portals have data-radix-popper-content-wrapper attribute
121+ const isInsideDropdown = ( target as Element ) . closest ?.(
122+ '[role="menu"], [data-radix-popper-content-wrapper]' ,
123+ ) ;
124+
125+ if ( isSearchExpanded && ! isInsideDropdown ) {
114126 setIsSearchExpanded ( false ) ;
115127 }
116128 } ;
@@ -140,6 +152,40 @@ const SearchBar: React.FC<SearchBarProps> = ({
140152 }
141153 } , [ metadataSearchTerms , onMetadataSearchTermsChange ] ) ;
142154
155+ const handleEditCriterion = ( criterion : SearchCriterion ) => {
156+ if ( disabled ) return ;
157+
158+ // Load the criterion into the input fields
159+ setEditingCriterionId ( criterion . id ) ;
160+ setCurrentCriterion ( {
161+ category : criterion . category ,
162+ operation : criterion . operation ,
163+ } ) ;
164+
165+ // Set the appropriate input value based on category
166+ if (
167+ criterion . category !== "metadata" &&
168+ criterion . category !== "log" &&
169+ criterion . category !== "service_name" &&
170+ criterion . category !== "service_environment"
171+ ) {
172+ // For metadata categories, split category and value
173+ setMetadataCategoryValue ( criterion . category ) ;
174+ setMetadataValue ( criterion . value ) ;
175+ setCurrentCriterion ( {
176+ category : "metadata" ,
177+ operation : criterion . operation ,
178+ } ) ;
179+ } else if ( criterion . category === "log" ) {
180+ setLogSearchValue ( criterion . value ) ;
181+ } else {
182+ setInputValue ( criterion . value ) ;
183+ }
184+
185+ // Ensure search bar is expanded
186+ setIsSearchExpanded ( true ) ;
187+ } ;
188+
143189 const handleAddCriterion = ( ) => {
144190 if ( disabled ) return ;
145191
@@ -154,24 +200,42 @@ const SearchBar: React.FC<SearchBarProps> = ({
154200 }
155201
156202 if ( categoryValue && currentCriterion . operation && searchValue ) {
157- const newCriterion : SearchCriterion = {
158- id : Date . now ( ) . toString ( ) ,
159- category : categoryValue ,
160- operation : currentCriterion . operation ,
161- value : searchValue ,
162- logicalOperator : criteria . length > 0 ? "AND" : undefined ,
163- } ;
164-
165- const newCriteria = [ ...criteria , newCriterion ] ;
203+ let newCriteria : SearchCriterion [ ] ;
204+ const operation = currentCriterion . operation ; // Guaranteed to be string due to the if condition
205+
206+ if ( editingCriterionId ) {
207+ // Update existing criterion
208+ newCriteria = criteria . map ( ( c ) =>
209+ c . id === editingCriterionId
210+ ? {
211+ ...c ,
212+ category : categoryValue ,
213+ operation : operation ,
214+ value : searchValue ,
215+ }
216+ : c ,
217+ ) ;
218+ setEditingCriterionId ( null ) ;
219+ } else {
220+ // Add new criterion
221+ const newCriterion : SearchCriterion = {
222+ id : Date . now ( ) . toString ( ) ,
223+ category : categoryValue ,
224+ operation : operation ,
225+ value : searchValue ,
226+ logicalOperator : criteria . length > 0 ? "AND" : undefined ,
227+ } ;
228+ newCriteria = [ ...criteria , newCriterion ] ;
229+ }
230+
166231 setCriteria ( newCriteria ) ;
167232 setCurrentCriterion ( { category : "log" , operation : "contains" } ) ;
168233 setInputValue ( "" ) ;
169234 setMetadataCategoryValue ( "" ) ;
170235 setMetadataValue ( "" ) ;
171- // Clear logSearchValue when adding a criterion
172236 setLogSearchValue ( "" ) ;
173237 onSearch ( newCriteria ) ;
174- // Collapse search after adding criterion
238+ // Collapse search after adding/updating criterion
175239 setIsSearchExpanded ( false ) ;
176240 }
177241 } ;
@@ -181,6 +245,17 @@ const SearchBar: React.FC<SearchBarProps> = ({
181245
182246 const newCriteria = criteria . filter ( ( c ) => c . id !== id ) ;
183247 setCriteria ( newCriteria ) ;
248+
249+ // If we're editing the criterion being removed, clear the edit state
250+ if ( editingCriterionId === id ) {
251+ setEditingCriterionId ( null ) ;
252+ setCurrentCriterion ( { category : "log" , operation : "contains" } ) ;
253+ setInputValue ( "" ) ;
254+ setMetadataCategoryValue ( "" ) ;
255+ setMetadataValue ( "" ) ;
256+ setLogSearchValue ( "" ) ;
257+ }
258+
184259 // If no criteria left, default back to log category
185260 if ( newCriteria . length === 0 ) {
186261 setCurrentCriterion ( { category : "log" , operation : "contains" } ) ;
@@ -237,7 +312,10 @@ const SearchBar: React.FC<SearchBarProps> = ({
237312 { criterion . logicalOperator }
238313 </ span >
239314 ) }
240- < div className = "flex items-center space-x-1 bg-gray-100 dark:bg-gray-700 rounded-md px-2 py-1" >
315+ < div
316+ className = "flex items-center space-x-1 rounded-md px-2 py-1 cursor-pointer transition-all bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600"
317+ onClick = { ( ) => handleEditCriterion ( criterion ) }
318+ >
241319 < span className = "text-xs font-medium text-gray-700 dark:text-gray-300" >
242320 { getCategoryLabel ( criterion . category ) }
243321 </ span >
@@ -250,7 +328,10 @@ const SearchBar: React.FC<SearchBarProps> = ({
250328 < Button
251329 variant = "ghost"
252330 size = "icon"
253- onClick = { ( ) => handleRemoveCriterion ( criterion . id ) }
331+ onClick = { ( e ) => {
332+ e . stopPropagation ( ) ;
333+ handleRemoveCriterion ( criterion . id ) ;
334+ } }
254335 className = "ml-0.5 h-4 w-4 p-0 opacity-70 transition-opacity hover:opacity-100 flex-shrink-0"
255336 disabled = { disabled }
256337 >
@@ -427,7 +508,14 @@ const SearchBar: React.FC<SearchBarProps> = ({
427508 if ( currentCriterion . category === "log" ) {
428509 return operation . value === "contains" ;
429510 }
430- // For other categories, show '=' operation
511+ // For service_name and service_environment, show both operations
512+ if (
513+ currentCriterion . category === "service_name" ||
514+ currentCriterion . category === "service_environment"
515+ ) {
516+ return true ; // Show both '=' and 'contains'
517+ }
518+ // For other categories (metadata), show '=' operation only
431519 return operation . value === "=" ;
432520 } ) . map ( ( operation ) => (
433521 < DropdownMenuRadioItem
@@ -460,7 +548,7 @@ const SearchBar: React.FC<SearchBarProps> = ({
460548 < div className = "flex items-center gap-1" >
461549 < span > Press</ span >
462550 < Kbd > Enter</ Kbd >
463- < span > to add</ span >
551+ < span > to { editingCriterionId ? "update" : " add" } </ span >
464552 </ div >
465553 </ TooltipContent >
466554 </ Tooltip >
@@ -484,7 +572,7 @@ const SearchBar: React.FC<SearchBarProps> = ({
484572 < div className = "flex items-center gap-1" >
485573 < span > Press</ span >
486574 < Kbd > Enter</ Kbd >
487- < span > to add</ span >
575+ < span > to { editingCriterionId ? "update" : " add" } </ span >
488576 </ div >
489577 </ TooltipContent >
490578 </ Tooltip >
@@ -509,7 +597,7 @@ const SearchBar: React.FC<SearchBarProps> = ({
509597 < div className = "flex items-center gap-1" >
510598 < span > Press</ span >
511599 < Kbd > Enter</ Kbd >
512- < span > to add</ span >
600+ < span > to { editingCriterionId ? "update" : " add" } </ span >
513601 </ div >
514602 </ TooltipContent >
515603 </ Tooltip >
@@ -522,6 +610,8 @@ const SearchBar: React.FC<SearchBarProps> = ({
522610 onClick = { ( ) => {
523611 // Clear all completed criteria
524612 setCriteria ( [ ] ) ;
613+ // Clear editing state
614+ setEditingCriterionId ( null ) ;
525615 // Clear current criterion being built and reset to default
526616 setCurrentCriterion ( { category : "log" , operation : "contains" } ) ;
527617 // Clear input value
0 commit comments