1
1
import { useCallback , useEffect , useRef , useState } from "react" ;
2
2
import Modal from "components/ui/Modal" ;
3
- import { Button , Textarea , Badge } from "@tremor/react" ;
3
+ import { Button , Textarea } from "@tremor/react" ;
4
4
import QueryBuilder , {
5
5
Field ,
6
6
Operator ,
@@ -19,14 +19,16 @@ import {
19
19
reverseSeverityMapping ,
20
20
} from "./models" ;
21
21
import { XMarkIcon , TrashIcon } from "@heroicons/react/24/outline" ;
22
- import { FiSave } from "react-icons/fi" ;
23
22
import { TbDatabaseImport } from "react-icons/tb" ;
24
23
import Select , { components , MenuListProps } from "react-select" ;
25
24
26
25
import { IoSearchOutline } from "react-icons/io5" ;
27
26
import { FiExternalLink } from "react-icons/fi" ;
28
27
import { usePathname , useRouter , useSearchParams } from "next/navigation" ;
29
28
import { toast } from "react-toastify" ;
29
+ import { CornerDownLeft } from "lucide-react" ;
30
+ import { Link } from "@/components/ui" ;
31
+ import { DocumentTextIcon } from "@heroicons/react/24/outline" ;
30
32
31
33
const staticOptions = [
32
34
{ value : 'severity > "info"' , label : 'severity > "info"' } ,
@@ -493,8 +495,8 @@ export const AlertsRulesBuilder = ({
493
495
operators : getOperators ( id ) ,
494
496
} ) )
495
497
: customFields
496
- ? customFields
497
- : [ ] ;
498
+ ? customFields
499
+ : [ ] ;
498
500
499
501
const onImportSQL = ( ) => {
500
502
setImportSQLOpen ( true ) ;
@@ -551,35 +553,107 @@ export const AlertsRulesBuilder = ({
551
553
} ;
552
554
553
555
return (
554
- < div className = "flex flex-col gap-y-2 w-full justify-end" >
555
- < Modal
556
- isOpen = { isGUIOpen }
557
- onClose = { ( ) => setIsGUIOpen ( false ) }
558
- className = "w-[50%] max-w-screen-2xl max-h-[710px] transform overflow-auto ring-tremor bg-white p-6 text-left align-middle shadow-tremor transition-all rounded-xl"
559
- title = "Query Builder"
560
- >
561
- < div className = "space-y-2 pt-4" >
562
- < div className = "max-h-96 overflow-auto" >
563
- < QueryBuilder
564
- query = { query }
565
- onQueryChange = { ( newQuery ) => setQuery ( newQuery ) }
566
- fields = { fields }
567
- addRuleToNewGroups
568
- showCombinatorsBetweenRules = { false }
569
- />
556
+ < >
557
+ < div className = "flex flex-col gap-y-2 w-full justify-end" >
558
+ { /* Docs */ }
559
+ < div className = "flex flex-wrap items-start gap-x-2" >
560
+ < div className = "flex flex-wrap gap-2 items-center relative flex-grow" >
561
+ { /* Textarea and error message container */ }
562
+ < div className = "flex-grow relative" ref = { wrapperRef } >
563
+ < Textarea
564
+ ref = { textAreaRef }
565
+ rows = { 1 }
566
+ className = "resize-none overflow-hidden w-full pr-9 min-h-[38px]" // Padding for clear button and height to match the button height
567
+ value = { celRules }
568
+ onValueChange = { onValueChange }
569
+ onKeyDown = { handleKeyDown }
570
+ placeholder = 'Use CEL to filter your alerts e.g. source.contains("kibana").'
571
+ error = { ! isValidCEL }
572
+ onFocus = { ( ) => setShowSuggestions ( true ) }
573
+ />
574
+ { celRules && (
575
+ < button
576
+ onClick = { handleClearInput }
577
+ className = "absolute top-0 right-0 w-9 h-[38px] flex items-center justify-center text-gray-400 hover:text-gray-600" // Position to the left of the Enter to apply badge
578
+ >
579
+ < XMarkIcon className = "h-4 w-4" />
580
+ </ button >
581
+ ) }
582
+ { showSuggestions && (
583
+ < div className = "absolute z-10 w-full" >
584
+ < Select
585
+ options = { staticOptions }
586
+ onChange = { handleSelectChange }
587
+ menuIsOpen = { true }
588
+ components = { minimal ? undefined : customComponents }
589
+ onBlur = { ( ) => setShowSuggestions ( false ) }
590
+ styles = { customStyles }
591
+ />
592
+ </ div >
593
+ ) }
594
+ { ! isValidCEL && (
595
+ < div className = "text-red-500 text-sm absolute bottom-0 left-0 transform translate-y-full" >
596
+ Invalid Common Expression Logic expression.
597
+ </ div >
598
+ ) }
599
+ < div className = "flex items-center justify-between pt-1 px-2" >
600
+ < Link
601
+ href = "https://docs.keephq.dev/overview/presets"
602
+ target = "_blank"
603
+ rel = "noreferrer noopener"
604
+ className = "text-xs text-tremor-muted"
605
+ icon = { DocumentTextIcon }
606
+ >
607
+ CEL Documentation
608
+ </ Link >
609
+ < span className = "text-xs text-gray-400" >
610
+ < CornerDownLeft className = "h-3 w-3 mr-1 inline-block" />
611
+ Enter to apply
612
+ </ span >
613
+ </ div >
614
+ </ div >
570
615
</ div >
571
- < div className = "inline-flex justify-end" >
616
+
617
+ { /* Buttons next to the Textarea */ }
618
+ { showSqlImport && (
572
619
< Button
573
620
color = "orange"
574
- onClick = { onGenerateQuery }
575
- disabled = { ! query . rules . length }
621
+ variant = "secondary"
622
+ type = "button"
623
+ onClick = { onImportSQL }
624
+ icon = { TbDatabaseImport }
625
+ size = "sm"
626
+ tooltip = "Import from SQL"
576
627
>
577
- Generate Query
628
+ Import from SQL
578
629
</ Button >
579
- </ div >
630
+ ) }
631
+ { showSave && (
632
+ < Button
633
+ color = "orange"
634
+ size = "sm"
635
+ disabled = { ! celRules . length }
636
+ onClick = { ( ) => validateAndOpenSaveModal ( celRules ) }
637
+ tooltip = "Save current filter as a preset"
638
+ >
639
+ Save
640
+ </ Button >
641
+ ) }
642
+ { selectedPreset &&
643
+ selectedPreset . name &&
644
+ selectedPreset ?. name !== "deleted" &&
645
+ selectedPreset ?. name !== "feed" &&
646
+ selectedPreset ?. name !== "dismissed" &&
647
+ deletePreset && (
648
+ < Button
649
+ icon = { TrashIcon }
650
+ color = "orange"
651
+ title = "Delete preset"
652
+ onClick = { async ( ) => await deletePreset ( selectedPreset ! . id ! ) }
653
+ > </ Button >
654
+ ) }
580
655
</ div >
581
- </ Modal >
582
-
656
+ </ div >
583
657
{ /* Import SQL */ }
584
658
< Modal
585
659
isOpen = { isImportSQLOpen }
@@ -608,111 +682,33 @@ export const AlertsRulesBuilder = ({
608
682
</ div >
609
683
</ Modal >
610
684
611
- { /* Docs */ }
612
- < div className = "flex flex-wrap items-center gap-x-2" >
613
- < div className = "flex flex-wrap gap-2 items-center relative flex-grow" >
614
- { /* CEL badge and (i) icon container */ }
615
- < div className = "flex items-center space-x-2" >
616
- < Badge
617
- key = { "cel" }
618
- size = "md"
619
- className = "cursor-pointer"
620
- color = "orange"
621
- tooltip = "Click for documentation"
622
- onClick = { ( ) =>
623
- window . open (
624
- "https://docs.keephq.dev/overview/presets" ,
625
- "_blank"
626
- )
627
- }
628
- >
629
- CEL
630
- </ Badge >
631
- </ div >
632
-
633
- { /* Textarea and error message container */ }
634
- < div className = "flex-grow relative" ref = { wrapperRef } >
635
- < Textarea
636
- ref = { textAreaRef }
637
- rows = { 1 }
638
- className = "resize-none overflow-hidden w-full pr-40" // Provide enough padding to the right
639
- value = { celRules }
640
- onValueChange = { onValueChange }
641
- onKeyDown = { handleKeyDown }
642
- placeholder = 'Use CEL to filter your alerts e.g. source.contains("kibana").'
643
- error = { ! isValidCEL }
644
- onFocus = { ( ) => setShowSuggestions ( true ) }
685
+ < Modal
686
+ isOpen = { isGUIOpen }
687
+ onClose = { ( ) => setIsGUIOpen ( false ) }
688
+ className = "w-[50%] max-w-screen-2xl max-h-[710px] transform overflow-auto ring-tremor bg-white p-6 text-left align-middle shadow-tremor transition-all rounded-xl"
689
+ title = "Query Builder"
690
+ >
691
+ < div className = "space-y-2 pt-4" >
692
+ < div className = "max-h-96 overflow-auto" >
693
+ < QueryBuilder
694
+ query = { query }
695
+ onQueryChange = { ( newQuery ) => setQuery ( newQuery ) }
696
+ fields = { fields }
697
+ addRuleToNewGroups
698
+ showCombinatorsBetweenRules = { false }
645
699
/>
646
- { showSuggestions && (
647
- < div className = "absolute z-10 w-full" >
648
- < Select
649
- options = { staticOptions }
650
- onChange = { handleSelectChange }
651
- menuIsOpen = { true }
652
- components = { minimal ? undefined : customComponents }
653
- onBlur = { ( ) => setShowSuggestions ( false ) }
654
- styles = { customStyles }
655
- />
656
- </ div >
657
- ) }
658
- { ! isValidCEL && (
659
- < div className = "text-red-500 text-sm absolute bottom-0 left-0 transform translate-y-full" >
660
- Invalid Common Expression Logic expression.
661
- </ div >
662
- ) }
663
- { celRules && (
664
- < button
665
- onClick = { handleClearInput }
666
- className = "absolute right-36 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600" // Position to the left of the Enter to apply badge
667
- >
668
- < XMarkIcon className = "h-4 w-4" />
669
- </ button >
670
- ) }
671
- < Badge
672
- size = "md"
700
+ </ div >
701
+ < div className = "inline-flex justify-end" >
702
+ < Button
673
703
color = "orange"
674
- className = "absolute right-2 top-1/2 transform -translate-y-1/2" // Position to the far right inside the padding area
704
+ onClick = { onGenerateQuery }
705
+ disabled = { ! query . rules . length }
675
706
>
676
- Enter to apply
677
- </ Badge >
707
+ Generate Query
708
+ </ Button >
678
709
</ div >
679
710
</ div >
680
-
681
- { /* Buttons next to the Textarea */ }
682
- { showSave && (
683
- < Button
684
- icon = { FiSave }
685
- color = "orange"
686
- size = "sm"
687
- disabled = { ! celRules . length }
688
- onClick = { ( ) => validateAndOpenSaveModal ( celRules ) }
689
- tooltip = "Save current filter as a view"
690
- />
691
- ) }
692
- { selectedPreset &&
693
- selectedPreset . name &&
694
- selectedPreset ?. name !== "deleted" &&
695
- selectedPreset ?. name !== "feed" &&
696
- selectedPreset ?. name !== "dismissed" &&
697
- deletePreset && (
698
- < Button
699
- icon = { TrashIcon }
700
- color = "orange"
701
- title = "Delete preset"
702
- onClick = { async ( ) => await deletePreset ( selectedPreset ! . id ! ) }
703
- > </ Button >
704
- ) }
705
- { showSqlImport && (
706
- < Button
707
- color = "orange"
708
- type = "button"
709
- onClick = { onImportSQL }
710
- icon = { TbDatabaseImport }
711
- size = "sm"
712
- tooltip = "Import from SQL"
713
- />
714
- ) }
715
- </ div >
716
- </ div >
711
+ </ Modal >
712
+ </ >
717
713
) ;
718
714
} ;
0 commit comments