mirror of https://github.com/grafana/grafana.git
				
				
				
			Alerting: Fix image embed in email template. (#50370)
The ng_alert_notification email template did not include templating for linked or embedded images. This change updates that. Additionally, this change supports embedding an image for each alert in an email batch. Fixes #50315
This commit is contained in:
		
							parent
							
								
									2b73326785
								
							
						
					
					
						commit
						ecf080825e
					
				|  | @ -89,12 +89,11 @@ func NewEmailNotifier(config *EmailConfig, ns notifications.EmailSender, images | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Notify sends the alert notification.
 | // Notify sends the alert notification.
 | ||||||
| func (en *EmailNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { | func (en *EmailNotifier) Notify(ctx context.Context, alerts ...*types.Alert) (bool, error) { | ||||||
| 	var tmplErr error | 	var tmplErr error | ||||||
| 	tmpl, data := TmplText(ctx, en.tmpl, as, en.log, &tmplErr) | 	tmpl, data := TmplText(ctx, en.tmpl, alerts, en.log, &tmplErr) | ||||||
| 
 | 
 | ||||||
| 	subject := tmpl(en.Subject) | 	subject := tmpl(en.Subject) | ||||||
| 
 |  | ||||||
| 	alertPageURL := en.tmpl.ExternalURL.String() | 	alertPageURL := en.tmpl.ExternalURL.String() | ||||||
| 	ruleURL := en.tmpl.ExternalURL.String() | 	ruleURL := en.tmpl.ExternalURL.String() | ||||||
| 	u, err := url.Parse(en.tmpl.ExternalURL.String()) | 	u, err := url.Parse(en.tmpl.ExternalURL.String()) | ||||||
|  | @ -108,6 +107,26 @@ func (en *EmailNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, | ||||||
| 		en.log.Debug("failed to parse external URL", "url", en.tmpl.ExternalURL.String(), "err", err.Error()) | 		en.log.Debug("failed to parse external URL", "url", en.tmpl.ExternalURL.String(), "err", err.Error()) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// Extend alerts data with images, if available.
 | ||||||
|  | 	var embeddedFiles []string | ||||||
|  | 	_ = withStoredImages(ctx, en.log, en.images, | ||||||
|  | 		func(index int, image *ngmodels.Image) error { | ||||||
|  | 			if image != nil { | ||||||
|  | 				if len(image.URL) != 0 { | ||||||
|  | 					data.Alerts[index].ImageURL = image.URL | ||||||
|  | 				} else if len(image.Path) != 0 { | ||||||
|  | 					_, err := os.Stat(image.Path) | ||||||
|  | 					if err == nil { | ||||||
|  | 						data.Alerts[index].EmbeddedImage = path.Base(image.Path) | ||||||
|  | 						embeddedFiles = append(embeddedFiles, image.Path) | ||||||
|  | 					} else { | ||||||
|  | 						en.log.Warn("failed to get image file for email attachment", "file", image.Path, "err", err) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			return nil | ||||||
|  | 		}, alerts...) | ||||||
|  | 
 | ||||||
| 	cmd := &models.SendEmailCommandSync{ | 	cmd := &models.SendEmailCommandSync{ | ||||||
| 		SendEmailCommand: models.SendEmailCommand{ | 		SendEmailCommand: models.SendEmailCommand{ | ||||||
| 			Subject: subject, | 			Subject: subject, | ||||||
|  | @ -123,34 +142,13 @@ func (en *EmailNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, | ||||||
| 				"RuleUrl":           ruleURL, | 				"RuleUrl":           ruleURL, | ||||||
| 				"AlertPageUrl":      alertPageURL, | 				"AlertPageUrl":      alertPageURL, | ||||||
| 			}, | 			}, | ||||||
| 			To:          en.Addresses, | 			EmbeddedFiles: embeddedFiles, | ||||||
| 			SingleEmail: en.SingleEmail, | 			To:            en.Addresses, | ||||||
| 			Template:    "ng_alert_notification", | 			SingleEmail:   en.SingleEmail, | ||||||
|  | 			Template:      "ng_alert_notification", | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// TODO: modify the email sender code to support multiple file or image URL
 |  | ||||||
| 	// fields. We cannot use images from every alert yet.
 |  | ||||||
| 	_ = withStoredImage(ctx, en.log, en.images, |  | ||||||
| 		func(index int, image *ngmodels.Image) error { |  | ||||||
| 			if image == nil { |  | ||||||
| 				return nil |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			if len(image.URL) != 0 { |  | ||||||
| 				cmd.Data["ImageLink"] = image.URL |  | ||||||
| 			} else if len(image.Path) != 0 { |  | ||||||
| 				file, err := os.Stat(image.Path) |  | ||||||
| 				if err == nil { |  | ||||||
| 					cmd.EmbeddedFiles = []string{image.Path} |  | ||||||
| 					cmd.Data["EmbeddedImage"] = file.Name() |  | ||||||
| 				} else { |  | ||||||
| 					en.log.Warn("failed to access email notification image attachment data", "err", err) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			return nil |  | ||||||
| 		}, 0, as...) |  | ||||||
| 
 |  | ||||||
| 	if tmplErr != nil { | 	if tmplErr != nil { | ||||||
| 		en.log.Warn("failed to template email message", "err", tmplErr.Error()) | 		en.log.Warn("failed to template email message", "err", tmplErr.Error()) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -18,18 +18,19 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type ExtendedAlert struct { | type ExtendedAlert struct { | ||||||
| 	Status       string      `json:"status"` | 	Status        string      `json:"status"` | ||||||
| 	Labels       template.KV `json:"labels"` | 	Labels        template.KV `json:"labels"` | ||||||
| 	Annotations  template.KV `json:"annotations"` | 	Annotations   template.KV `json:"annotations"` | ||||||
| 	StartsAt     time.Time   `json:"startsAt"` | 	StartsAt      time.Time   `json:"startsAt"` | ||||||
| 	EndsAt       time.Time   `json:"endsAt"` | 	EndsAt        time.Time   `json:"endsAt"` | ||||||
| 	GeneratorURL string      `json:"generatorURL"` | 	GeneratorURL  string      `json:"generatorURL"` | ||||||
| 	Fingerprint  string      `json:"fingerprint"` | 	Fingerprint   string      `json:"fingerprint"` | ||||||
| 	SilenceURL   string      `json:"silenceURL"` | 	SilenceURL    string      `json:"silenceURL"` | ||||||
| 	DashboardURL string      `json:"dashboardURL"` | 	DashboardURL  string      `json:"dashboardURL"` | ||||||
| 	PanelURL     string      `json:"panelURL"` | 	PanelURL      string      `json:"panelURL"` | ||||||
| 	ValueString  string      `json:"valueString"` | 	ValueString   string      `json:"valueString"` | ||||||
| 	ImageURL     string      `json:"imageURL,omitempty"` | 	ImageURL      string      `json:"imageURL,omitempty"` | ||||||
|  | 	EmbeddedImage string      `json:"embeddedImage,omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type ExtendedAlerts []ExtendedAlert | type ExtendedAlerts []ExtendedAlert | ||||||
|  |  | ||||||
|  | @ -210,6 +210,21 @@ text-decoration: underline; | ||||||
| {{Subject .Subject "{{.Title}}"}} | {{Subject .Subject "{{.Title}}"}} | ||||||
| 
 | 
 | ||||||
| {{ define "alert" }} | {{ define "alert" }} | ||||||
|  | 
 | ||||||
|  |   {{if ne .ImageURL "" }} | ||||||
|  |   <tr style="vertical-align: top; padding: 0;" align="left"> | ||||||
|  |     <td colspan="2" style="word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-weight: normal; line-height: 19px; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; margin: 0; padding: 24px 0 0;" align="left" valign="top"> | ||||||
|  |        <img src="{{.ImageURL}}" class="fluid-centered" alt="Alerting Panel" style="outline: none !important; text-decoration: none !important; -ms-interpolation-mode: bicubic; width: auto; clear: both; display: block; border: 0;" align="left" /> | ||||||
|  | 		</td> | ||||||
|  | 	</tr> | ||||||
|  |   {{end}} | ||||||
|  |   {{if ne .EmbeddedImage "" }} | ||||||
|  |   <tr style="vertical-align: top; padding: 0;" align="left"> | ||||||
|  |     <td colspan="2" style="word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-weight: normal; line-height: 19px; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; margin: 0; padding: 24px 0 0;" align="left" valign="top"> | ||||||
|  |        <img src="cid:{{.EmbeddedImage}}" alt="Alerting Chart Attached Below" style="outline: none !important; text-decoration: none !important; -ms-interpolation-mode: bicubic; width: auto; clear: both; display: block; border: 0;" align="left" /> | ||||||
|  | 		</td> | ||||||
|  | 	</tr> | ||||||
|  | 	{{end}} | ||||||
|   <tr style="vertical-align: top; padding: 0;" align="left"> |   <tr style="vertical-align: top; padding: 0;" align="left"> | ||||||
|     <td colspan="2" class="value" style="word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-weight: normal; line-height: 19px; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; margin: 0; padding: 24px 0 0;" align="left" valign="top"> |     <td colspan="2" class="value" style="word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-weight: normal; line-height: 19px; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; margin: 0; padding: 24px 0 0;" align="left" valign="top"> | ||||||
|       <span class="value-heading" style="font-weight: bold;">Value:</span> <span class="value-value" style="padding-left: 8px;">{{ .ValueString }}</span> |       <span class="value-heading" style="font-weight: bold;">Value:</span> <span class="value-value" style="padding-left: 8px;">{{ .ValueString }}</span> | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue