@ -8,7 +8,7 @@ import {
Button ,
Button ,
Drawer ,
Drawer ,
IconButton ,
IconButton ,
Input ,
Label ,
RadioButtonGroup ,
RadioButtonGroup ,
Select ,
Select ,
Stack ,
Stack ,
@ -16,6 +16,7 @@ import {
TextArea ,
TextArea ,
useStyles2 ,
useStyles2 ,
} from '@grafana/ui' ;
} from '@grafana/ui' ;
import { Trans } from 'app/core/internationalization' ;
import {
import {
trackEditInputWithTemplate ,
trackEditInputWithTemplate ,
trackUseCustomInputInTemplate ,
trackUseCustomInputInTemplate ,
@ -34,6 +35,8 @@ import { defaultPayloadString } from '../../TemplateForm';
import { TemplateContentAndPreview } from './TemplateContentAndPreview' ;
import { TemplateContentAndPreview } from './TemplateContentAndPreview' ;
import { getTemplateName , getUseTemplateText , matchesOnlyOneTemplate , parseTemplates } from './utils' ;
import { getTemplateName , getUseTemplateText , matchesOnlyOneTemplate , parseTemplates } from './utils' ;
const { useGetDefaultTemplatesQuery } = templatesApi ;
interface TemplatesPickerProps {
interface TemplatesPickerProps {
onSelect : ( temnplate : string ) = > void ;
onSelect : ( temnplate : string ) = > void ;
option : NotificationChannelOption ;
option : NotificationChannelOption ;
@ -45,27 +48,23 @@ export function TemplatesPicker({ onSelect, option, valueInForm }: TemplatesPick
setShowTemplates ( true ) ;
setShowTemplates ( true ) ;
trackEditInputWithTemplate ( ) ;
trackEditInputWithTemplate ( ) ;
} ;
} ;
const handleClose = ( ) = > setShowTemplates ( false ) ;
return (
return (
< >
< >
< Button
< Button
icon = "edit"
icon = "edit"
tooltip = { 'Edit using existing templates.' }
tooltip = { ` Edit ${ option . label . toLowerCase ( ) } using existing templates. ` }
onClick = { onClick }
onClick = { onClick }
variant = "secondary"
variant = "secondary"
size = "sm"
size = "sm"
aria - label = { 'Select available template from the list of available templates.' }
>
>
{ ` Edit ${ option . label } ` }
{ ` Edit ${ option . label } ` }
< / Button >
< / Button >
{ showTemplates && (
{ showTemplates && (
< Drawer title = { ` Edit ${ option . label } ` } size = "md" onClose = { ( ) = > setShowTemplates ( false ) } >
< Drawer title = { ` Edit ${ option . label } ` } size = "md" onClose = { handleClose } >
< TemplateSelector
< TemplateSelector onSelect = { onSelect } onClose = { handleClose } option = { option } valueInForm = { valueInForm } / >
onSelect = { onSelect }
onClose = { ( ) = > setShowTemplates ( false ) }
option = { option }
valueInForm = { valueInForm }
/ >
< / Drawer >
< / Drawer >
) }
) }
< / >
< / >
@ -102,10 +101,6 @@ export function getTemplateOptions(templateFiles: NotificationTemplate[], defaul
// return the sum of default and custom templates
// return the sum of default and custom templates
return Array . from ( templateMap . values ( ) ) ;
return Array . from ( templateMap . values ( ) ) ;
}
}
function getContentFromOptions ( name : string , options : Array < SelectableValue < Template > > ) {
const template = options . find ( ( option ) = > option . label === name ) ;
return template ? . value ? . content ? ? '' ;
}
export interface Template {
export interface Template {
name : string ;
name : string ;
@ -117,40 +112,44 @@ interface TemplateSelectorProps {
option : NotificationChannelOption ;
option : NotificationChannelOption ;
valueInForm : string ;
valueInForm : string ;
}
}
function TemplateSelector ( { onSelect , onClose , option , valueInForm } : TemplateSelectorProps ) {
function TemplateSelector ( { onSelect , onClose , option , valueInForm } : TemplateSelectorProps ) {
const styles = useStyles2 ( getStyles ) ;
const styles = useStyles2 ( getStyles ) ;
const useGetDefaultTemplatesQuery = templatesApi . endpoints . getDefaultTemplates . useQuery ;
const valueInFormIsCustom = Boolean ( valueInForm ) && ! matchesOnlyOneTemplate ( valueInForm ) ;
const [ template , setTemplate ] = useState < Template | undefined > ( undefined ) ;
const [ template , setTemplate ] = useState < SelectableValue < Template > | undefined > ( undefined ) ;
const [ inputToUpdate , setInputToUpdate ] = useState < string > ( '' ) ;
const [ customTemplateValue , setCustomTemplateValue ] = useState < string > ( valueInForm ) ;
const [ inputToUpdateCustom , setInputToUpdateCustom ] = useState < string > ( valueInForm ) ;
const { selectedAlertmanager } = useAlertmanager ( ) ;
const { selectedAlertmanager } = useAlertmanager ( ) ;
const { data = [ ] , error , isLoading } = useNotificationTemplates ( { alertmanager : selectedAlertmanager ! } ) ;
const { data = [ ] , error , isLoading } = useNotificationTemplates ( { alertmanager : selectedAlertmanager ! } ) ;
const { data : defaultTemplates } = useGetDefaultTemplatesQuery ( ) ;
const { data : defaultTemplates } = useGetDefaultTemplatesQuery ( ) ;
const [ templateOption , setTemplateOption ] = useState < TemplateFieldOption > ( 'Existing' ) ;
const [ templateOption , setTemplateOption ] = useState < TemplateFieldOption | undefined > (
valueInFormIsCustom ? 'Custom' : 'Existing'
) ;
const [ _ , copyToClipboard ] = useCopyToClipboard ( ) ;
const [ _ , copyToClipboard ] = useCopyToClipboard ( ) ;
const templateOptions : Array < SelectableValue < TemplateFieldOption > > = [
const templateOptions : Array < SelectableValue < TemplateFieldOption > > = [
{
{
label : 'Select existing template' ,
label : 'Select existing template' ,
ariaLabel : 'Select existing template' ,
value : 'Existing' ,
value : 'Existing' ,
description : ` Select a single template and preview it, or copy it to paste it in the custom tab. ${ templateOption === 'Existing' ? 'Clicking Save will save your changes to the selected template.' : '' } ` ,
description : ` Select a single template and preview it, or copy it to paste it in the custom tab. ${ templateOption === 'Existing' ? 'Clicking Save will save your changes to the selected template.' : '' } ` ,
} ,
} ,
{
{
label : ` Enter custom ${ option . label . toLowerCase ( ) } ` ,
label : ` Enter custom ${ option . label . toLowerCase ( ) } ` ,
ariaLabel : ` Enter custom ${ option . label . toLowerCase ( ) } ` ,
value : 'Custom' ,
value : 'Custom' ,
description : ` Enter custom ${ option . label . toLowerCase ( ) } . ${ templateOption === 'Custom' ? 'Clicking Save will save the custom value only.' : '' } ` ,
description : ` Enter custom ${ option . label . toLowerCase ( ) } . ${ templateOption === 'Custom' ? 'Clicking Save will save the custom value only.' : '' } ` ,
} ,
} ,
] ;
] ;
useEffect ( ( ) = > {
useEffect ( ( ) = > {
if ( template ) {
if ( template ? . value ? . name ) {
setInputToUpdat e ( getUseTemplateText ( template . name ) ) ;
setCustomTemplateValu e ( getUseTemplateText ( template . valu e . name ) ) ;
}
}
} , [ template ] ) ;
} , [ template ] ) ;
function onCustomTemplateChange ( customInput : string ) {
function onCustomTemplateChange ( customInput : string ) {
setInputToUpdateCustom ( customInput ) ;
setCustomTemplateValue ( customInput ) ;
}
}
const onTemplateOptionChange = ( option : TemplateFieldOption ) = > {
const onTemplateOptionChange = ( option : TemplateFieldOption ) = > {
@ -164,21 +163,14 @@ function TemplateSelector({ onSelect, onClose, option, valueInForm }: TemplateSe
return getTemplateOptions ( data , defaultTemplates ) ;
return getTemplateOptions ( data , defaultTemplates ) ;
} , [ data , defaultTemplates , isLoading , error ] ) ;
} , [ data , defaultTemplates , isLoading , error ] ) ;
// if we are using only one template, we should settemplate to that template
const defaultTemplateValue = useMemo ( ( ) = > {
useEffect ( ( ) = > {
if ( ! options . length || ! Boolean ( valueInForm ) || ! matchesOnlyOneTemplate ( valueInForm ) ) {
if ( Boolean ( valueInForm ) ) {
return null ;
if ( matchesOnlyOneTemplate ( valueInForm ) ) {
const name = getTemplateName ( valueInForm ) ;
setTemplate ( {
name ,
content : getContentFromOptions ( name , options ) ,
} ) ;
} else {
// if it's empty we default to select existing template
setTemplateOption ( 'Custom' ) ;
}
}
}
} , [ valueInForm , setTemplate , setTemplateOption , options ] ) ;
const nameOfTemplateInForm = getTemplateName ( valueInForm ) ;
return options . find ( ( option ) = > option . label === nameOfTemplateInForm ) || null ;
} , [ options , valueInForm ] ) ;
if ( error ) {
if ( error ) {
return < div > Error loading templates < / div > ;
return < div > Error loading templates < / div > ;
@ -202,26 +194,29 @@ function TemplateSelector({ onSelect, onClose, option, valueInForm }: TemplateSe
< Stack direction = "column" gap = { 1 } >
< Stack direction = "column" gap = { 1 } >
< Stack direction = "row" gap = { 1 } alignItems = "center" >
< Stack direction = "row" gap = { 1 } alignItems = "center" >
< Select < Template >
< Select < Template >
data - testid = "existing-templates-selector"
placeholder = "Choose template"
placeholder = "Choose template"
aria - label = "Choose template"
aria - label = "Choose template"
onChange = { ( value : SelectableValue < Template > , _ ) = > {
onChange = { ( value : SelectableValue < Template > , _ ) = > {
setTemplate ( value ? . value ) ;
setTemplate ( value ) ;
} }
} }
options = { options }
options = { options }
width = { 50 }
width = { 50 }
value = { template ? { label : template.name , value : template } : undefined }
defaultValue = { defaultTemplateValue }
/ >
/ >
< IconButton
< IconButton
tooltip = "Copy selected template to clipboard. You can use it in the custom tab."
tooltip = "Copy selected template to clipboard. You can use it in the custom tab."
onClick = { ( ) = > copyToClipboard ( getUseTemplateText ( template ? . name ? ? '' ) ) }
onClick = { ( ) = >
copyToClipboard ( getUseTemplateText ( template ? . value ? . name ? ? defaultTemplateValue ? . value ? . name ? ? '' ) )
}
name = "copy"
name = "copy"
/ >
/ >
< / Stack >
< / Stack >
< TemplateContentAndPreview
< TemplateContentAndPreview
templateContent = { template ? . content ? ? '' }
templateContent = { template ? . value ? . content ? ? defaultTemplateValue ? . value ? . content ? ? '' }
payload = { defaultPayloadString }
payload = { defaultPayloadString }
templateName = { template ? . name ? ? '' }
templateName = { template ? . value ? . name ? ? defaultTemplateValue ? . value ? . name ? ? '' }
setPayloadFormatError = { ( ) = > { } }
setPayloadFormatError = { ( ) = > { } }
className = { cx ( styles . templatePreview , styles . minEditorSize ) }
className = { cx ( styles . templatePreview , styles . minEditorSize ) }
payloadFormatError = { null }
payloadFormatError = { null }
@ -231,7 +226,7 @@ function TemplateSelector({ onSelect, onClose, option, valueInForm }: TemplateSe
< OptionCustomfield
< OptionCustomfield
option = { option }
option = { option }
onCustomTemplateChange = { onCustomTemplateChange }
onCustomTemplateChange = { onCustomTemplateChange }
initialValue = { inputToUpdateCustom }
initialValue = { customTemplateValue }
/ >
/ >
) }
) }
< / Stack >
< / Stack >
@ -242,13 +237,15 @@ function TemplateSelector({ onSelect, onClose, option, valueInForm }: TemplateSe
< Button
< Button
variant = "primary"
variant = "primary"
onClick = { ( ) = > {
onClick = { ( ) = > {
onSelect ( templateOption === 'Custom' ? inputToUpdateCustom : inputToUpdate ) ;
onClose ( ) ;
if ( templateOption === 'Custom' ) {
if ( templateOption === 'Custom' ) {
trackUseCustomInputInTemplate ( ) ;
trackUseCustomInputInTemplate ( ) ;
onSelect ( customTemplateValue ) ;
} else {
} else {
trackUseSingleTemplateInInput ( ) ;
trackUseSingleTemplateInInput ( ) ;
const name = template ? . value ? . name ? ? defaultTemplateValue ? . value ? . name ? ? '' ;
onSelect ( getUseTemplateText ( name ) ) ;
}
}
return onClose ( ) ;
} }
} }
>
>
Save
Save
@ -267,31 +264,21 @@ function OptionCustomfield({
onCustomTemplateChange ( customInput : string ) : void ;
onCustomTemplateChange ( customInput : string ) : void ;
initialValue : string ;
initialValue : string ;
} ) {
} ) {
switch ( option . element ) {
const id = ` custom-template- ${ option . label } ` ;
case 'textarea' :
return (
return (
< Stack direction = "column" gap = { 1 } >
< Stack direction = "row" gap = { 1 } alignItems = "center" >
< Label htmlFor = { id } >
< TextArea
< Trans i18nKey = "alerting.contact-points.custom-template-value" > Custom template value < / Trans >
placeholder = { option . placeholder }
< / Label >
onChange = { ( e ) = > onCustomTemplateChange ( e . currentTarget . value ) }
< TextArea
defaultValue = { initialValue }
id = { id }
/ >
label = "Custom template"
< / Stack >
placeholder = { option . placeholder }
) ;
onChange = { ( e ) = > onCustomTemplateChange ( e . currentTarget . value ) }
case 'input' :
defaultValue = { initialValue }
return (
/ >
< Stack direction = "row" gap = { 1 } alignItems = "center" >
< / Stack >
< Input
) ;
type = { option . inputType }
placeholder = { option . placeholder }
onChange = { ( e ) = > onCustomTemplateChange ( e . currentTarget . value ) }
defaultValue = { initialValue }
/ >
< / Stack >
) ;
default :
return null ;
}
}
}
interface WrapWithTemplateSelectionProps extends PropsWithChildren {
interface WrapWithTemplateSelectionProps extends PropsWithChildren {