Skip to content

Commit

Permalink
Merge pull request #15 from ianmcorvidae/periodic-notifications-tweaks
Browse files Browse the repository at this point in the history
Periodic notifications tweaks
  • Loading branch information
ianmcorvidae authored Sep 30, 2024
2 parents eb4fe78 + a52c295 commit d40317a
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 20 deletions.
40 changes: 36 additions & 4 deletions analyses.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ import (
// database through the GraphQL server. Shouldn't have timezone info.
const TimestampFromDBFormat = "2006-01-02T15:04:05"

// VICEURI is the base URI for VICE access
var VICEURI string

// AnalysesInit initializes the base URI for VICE access
func AnalysesInit(u string) {
VICEURI = u
}

// Job contains the information about an analysis that we're interested in.
type Job struct {
ID string `json:"id"`
Expand All @@ -43,19 +51,43 @@ type Job struct {
PeriodicPeriod int `json:"periodic_period"`
}

// getJobDuration takes a job and returns a duration string and the start time of the job
func getJobDuration(j *Job) (string, time.Time, error) {
func (j *Job) accessURL() (string, error) {
vice_uri, err := url.Parse(VICEURI)
if err != nil {
return "", errors.Wrapf(err, "Error parsing VICE URI from %s", VICEURI)
}
vice_uri.Host = j.Subdomain + "." + vice_uri.Host
return vice_uri.String(), nil
}

// getJobDuration takes a job and returns a duration string since the start of the job
func getJobDuration(j *Job) (string, error) {
starttime, err := time.ParseInLocation(TimestampFromDBFormat, j.StartDate, time.Local)
if err != nil {
return "", starttime, errors.Wrapf(err, "failed to parse start date %s", j.StartDate)
return "", errors.Wrapf(err, "failed to parse start date %s", j.StartDate)
}

// just print H(HH):MM format
dur := time.Since(starttime).Round(time.Minute)
h := dur / time.Hour
dur -= h * time.Hour
m := dur / time.Minute
return fmt.Sprintf("%d:%02d", h, m), starttime, nil
return fmt.Sprintf("%d:%02d", h, m), nil
}

// getRemainingDuration takes a job and returns a duration string until the planned end date
func getRemainingDuration(j *Job) (string, error) {
endtime, err := time.ParseInLocation(TimestampFromDBFormat, j.PlannedEndDate, time.Local)
if err != nil {
return "", errors.Wrapf(err, "failed to parse planned end date %s", j.PlannedEndDate)
}

// just print H(HH):MM format
dur := time.Until(endtime).Round(time.Minute)
h := dur / time.Hour
dur -= h * time.Hour
m := dur / time.Minute
return fmt.Sprintf("%d:%02d", h, m), nil
}

const jobsToKillQuery = `
Expand Down
44 changes: 38 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ notification_agent:
iplant_groups:
base: http://iplant-groups
user: grouper-user
k8s:
frontend:
base: ""
`

const warningSentKey = "warningsent"
Expand Down Expand Up @@ -68,18 +71,27 @@ func sendNotif(ctx context.Context, j *Job, status, subject, msg string, email b
}
sdmillis := sd.UnixNano() / 1000000

durString, _, err := getJobDuration(j)
durString, err := getJobDuration(j)
if err != nil {
return errors.Wrapf(err, "failed to parse job duration from %s", j.StartDate)
}
remainingString, err := getRemainingDuration(j)
if err != nil {
return errors.Wrapf(err, "failed to parse remaining time duration from %s", j.PlannedEndDate)
}

p := NewPayload()
p.AnalysisName = j.Name
p.AnalysisDescription = j.Description
p.AnalysisStatus = status
p.AnalysisStartDate = strconv.FormatInt(sdmillis, 10)
p.StartDate = strconv.FormatInt(sdmillis, 10)
p.AnalysisResultsFolder = j.ResultFolder
p.RunDuration = durString
p.EndDuration = remainingString
access_url, err := j.accessURL()
if err != nil && access_url != "" {
p.AccessURL = access_url
}
if email {
p.Email = user.Email
}
Expand Down Expand Up @@ -110,6 +122,7 @@ func ConfigureNotifications(cfg *viper.Viper, notifPath string) error {
return errors.Wrapf(err, "failed to parse %s", notifBase)
}
notifURL = notifURL.JoinPath(notifPath)

NotifsInit(notifURL.String())
return nil
}
Expand All @@ -129,6 +142,21 @@ func ConfigureUserLookups(cfg *viper.Viper) error {
return nil
}

func ConfigureAnalyses(cfg *viper.Viper) error {
viceBase := cfg.GetString("k8s.frontend.base")
if viceBase == "" {
AnalysesInit("")
return nil
}
viceURL, err := url.Parse(viceBase)
if err != nil {
return errors.Wrapf(err, "failed to parse %s", viceBase)
}

AnalysesInit(viceURL.String())
return nil
}

// SendKillNotification sends a notification to the user telling them that
// their job has been killed.
func SendKillNotification(ctx context.Context, j *Job, killNotifKey string) error {
Expand Down Expand Up @@ -173,19 +201,23 @@ func SendWarningNotification(ctx context.Context, j *Job) error {
}

func SendPeriodicNotification(ctx context.Context, j *Job) error {
durString, starttime, err := getJobDuration(j)
durString, err := getJobDuration(j)
if err != nil {
return err
}

remainingString, err := getRemainingDuration(j)
if err != nil {
return err
}

subject := fmt.Sprintf(PeriodicSubjectFormat, j.Name, starttime, durString)
subject := PeriodicSubjectFormat // this is a static/generic string

msg := fmt.Sprintf(
PeriodicMessageFormat,
j.Name,
j.ID,
starttime,
durString,
remainingString,
)

return sendNotif(ctx, j, j.Status, subject, msg, j.NotifyPeriodic, "analysis_periodic_notification")
Expand Down
18 changes: 8 additions & 10 deletions notifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,13 @@ const WarningSubjectFormat = "Analysis %s will terminate on %s (%s)."

// PeriodicMessageFormat is the parameterized message that gets sent to users
// when it's time to send a regular reminder the job is still running
// parameters: analysis name & ID, start date, duration
const PeriodicMessageFormat = `Analysis "%s" (%s) is still running.
// parameters: analysis name, current duration, duration until planned end date
const PeriodicMessageFormat = `Analysis "%s" has been running for %s and will stop in %s.`

This is a regularly scheduled reminder message to ensure you don't use up your quota.
This job has been running since %s (%s).`

// PeriodicSubjectFormat is the parameterized subject for the email that is sent
// PeriodicSubjectFormat is the subject for the email that is sent
// to users as a regular reminder of a running job
// parameters: analysis name, start date, duration
const PeriodicSubjectFormat = `Analysis %s is running since %s (%s)`
// no parameters, as it is vague on purpose to encourage opening the email with full details
const PeriodicSubjectFormat = `CyVerse: Your analysis is still running`

// Notification is a message intended as a notification to some upstream service
// or the DE UI.
Expand All @@ -69,9 +65,11 @@ type Payload struct {
AnalysisName string `json:"analysisname"`
AnalysisDescription string `json:"analysisdescription"`
AnalysisStatus string `json:"analysisstatus"`
AnalysisStartDate string `json:"analysisstartdate"`
StartDate string `json:"startdate"`
AnalysisResultsFolder string `json:"analysisresultsfolder"`
RunDuration string `json:"runduration"`
EndDuration string `json:"endduration"`
AccessURL string `json:"access_url"`
Email string `json:"email_address"`
Action string `json:"action"`
User string `json:"user"`
Expand Down

0 comments on commit d40317a

Please sign in to comment.