From 290225a104dfc3274da5cea74dcf362282b3ad3f Mon Sep 17 00:00:00 2001 From: Ian McEwen Date: Sun, 29 Sep 2024 14:24:05 -0700 Subject: [PATCH 1/3] Update periodic subject and message formats to match comms feedback --- analyses.go | 23 +++++++++++++++++++---- main.go | 14 +++++++++----- notifications.go | 14 +++++--------- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/analyses.go b/analyses.go index ddddfab..77e75ae 100644 --- a/analyses.go +++ b/analyses.go @@ -43,11 +43,11 @@ 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) { +// 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 @@ -55,7 +55,22 @@ func getJobDuration(j *Job) (string, time.Time, error) { 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 = ` diff --git a/main.go b/main.go index d095bcf..2bbd06d 100644 --- a/main.go +++ b/main.go @@ -68,7 +68,7 @@ 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) } @@ -173,19 +173,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 } - subject := fmt.Sprintf(PeriodicSubjectFormat, j.Name, starttime, durString) + remainingString, err := getRemainingDuration(j) + if err != nil { + return err + } + + 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") diff --git a/notifications.go b/notifications.go index bcd7bc6..0b1c830 100644 --- a/notifications.go +++ b/notifications.go @@ -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. You can terminate or extend the time limit here.` -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. From aff5e70619420f3335e84be9815f85c80e591abf Mon Sep 17 00:00:00 2001 From: Ian McEwen Date: Mon, 30 Sep 2024 10:16:46 -0700 Subject: [PATCH 2/3] Support remaining-duration in notifications, add access URL, improve dates --- analyses.go | 17 +++++++++++++++++ main.go | 30 +++++++++++++++++++++++++++++- notifications.go | 4 +++- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/analyses.go b/analyses.go index 77e75ae..2a2eb9a 100644 --- a/analyses.go +++ b/analyses.go @@ -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"` @@ -43,6 +51,15 @@ type Job struct { PeriodicPeriod int `json:"periodic_period"` } +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) diff --git a/main.go b/main.go index 2bbd06d..ca81b9c 100644 --- a/main.go +++ b/main.go @@ -40,6 +40,9 @@ notification_agent: iplant_groups: base: http://iplant-groups user: grouper-user +k8s: + frontend: + base: "" ` const warningSentKey = "warningsent" @@ -72,14 +75,23 @@ func sendNotif(ctx context.Context, j *Job, status, subject, msg string, email b 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 } @@ -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 } @@ -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 { diff --git a/notifications.go b/notifications.go index 0b1c830..7e5f663 100644 --- a/notifications.go +++ b/notifications.go @@ -65,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"` From a52c295760064a2cb49f6b718b17fb5dcfc2a910 Mon Sep 17 00:00:00 2001 From: Ian McEwen Date: Mon, 30 Sep 2024 10:27:35 -0700 Subject: [PATCH 3/3] Remove in-sonora 'click here' text from raw notification --- notifications.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notifications.go b/notifications.go index 7e5f663..9b735f3 100644 --- a/notifications.go +++ b/notifications.go @@ -40,7 +40,7 @@ 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, current duration, duration until planned end date -const PeriodicMessageFormat = `Analysis "%s" has been running for %s and will stop in %s. You can terminate or extend the time limit here.` +const PeriodicMessageFormat = `Analysis "%s" has been running for %s and will stop in %s.` // PeriodicSubjectFormat is the subject for the email that is sent // to users as a regular reminder of a running job