38275bd710
Remove dupulicate node IDs when sending notifications. Change-Id: I76c642e342081f8fdf8248443de8383b9e88eed2
123 lines
3.0 KiB
Go
123 lines
3.0 KiB
Go
// Copyright (C) 2022 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package nodeevents
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/zeebo/errs"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// CustomerioConfig handles customer.io credentials info.
|
|
type CustomerioConfig struct {
|
|
URL string `help:"the url for the customer.io endpoint to send node event data to" default:"https://track.customer.io/api/v1"`
|
|
SiteID string `help:"the account id for the customer.io api" default:""`
|
|
APIKey string `help:"api key for the customer.io api" default:""`
|
|
RequestTimeout time.Duration `help:"timeout for the http request to customer.io endpoint" default:"30s"`
|
|
}
|
|
|
|
// CustomerioNotifier notifies customer.io about node events.
|
|
type CustomerioNotifier struct {
|
|
log *zap.Logger
|
|
config CustomerioConfig
|
|
client *http.Client
|
|
}
|
|
|
|
// CustomerioBatch contains info regarding a batch of node events
|
|
// for a particular node operator email address.
|
|
type CustomerioBatch struct {
|
|
Name string `json:"name"`
|
|
Data CustomerioData `json:"data"`
|
|
}
|
|
|
|
// CustomerioData contains the satellite name and the node IDs that had an occurrence of the event.
|
|
type CustomerioData struct {
|
|
Satellite string `json:"satellite"`
|
|
NodeIDs string `json:"nodeIDs"`
|
|
}
|
|
|
|
// NewCustomerioNotifier is a constructor for CustomerioNotifier.
|
|
func NewCustomerioNotifier(log *zap.Logger, config CustomerioConfig) *CustomerioNotifier {
|
|
return &CustomerioNotifier{
|
|
log: log,
|
|
config: config,
|
|
client: &http.Client{
|
|
Timeout: config.RequestTimeout,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Notify sends node event data to customer.io.
|
|
func (c *CustomerioNotifier) Notify(ctx context.Context, satellite string, events []NodeEvent) (err error) {
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
if len(events) == 0 {
|
|
return nil
|
|
}
|
|
|
|
email := events[0].Email
|
|
eventName, err := events[0].Event.Name()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var nodeIDs string
|
|
idsMap := make(map[string]struct{})
|
|
for _, e := range events {
|
|
idStr := e.NodeID.String()
|
|
if _, ok := idsMap[idStr]; !ok {
|
|
idsMap[idStr] = struct{}{}
|
|
nodeIDs = nodeIDs + idStr + ","
|
|
}
|
|
}
|
|
nodeIDs = strings.TrimSuffix(nodeIDs, ",")
|
|
|
|
batch := CustomerioBatch{
|
|
Name: eventName,
|
|
Data: CustomerioData{
|
|
Satellite: satellite,
|
|
NodeIDs: nodeIDs,
|
|
},
|
|
}
|
|
data, err := json.Marshal(batch)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
url := c.config.URL + "/customers/" + email + "/events"
|
|
|
|
req, err := http.NewRequestWithContext(
|
|
ctx,
|
|
http.MethodPost,
|
|
url,
|
|
bytes.NewReader(data),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
req.Header.Add("Content-Type", "application/json")
|
|
req.SetBasicAuth(c.config.SiteID, c.config.APIKey)
|
|
|
|
resp, err := c.client.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { err = errs.Combine(err, resp.Body.Close()) }()
|
|
|
|
c.log.Info("batch sent to customer.io", zap.String("email", email), zap.String("event", eventName), zap.String("node IDs", nodeIDs))
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return errs.New("unexpected status code: %d", resp.StatusCode)
|
|
}
|
|
return err
|
|
|
|
}
|