diff options
| -rw-r--r-- | dashboard/app/email_test.go | 58 | ||||
| -rw-r--r-- | dashboard/app/entities.go | 1 | ||||
| -rw-r--r-- | dashboard/app/jobs.go | 59 | ||||
| -rw-r--r-- | dashboard/app/jobs_test.go | 9 | ||||
| -rw-r--r-- | dashboard/app/notifications_test.go | 30 | ||||
| -rw-r--r-- | dashboard/app/reporting.go | 103 | ||||
| -rw-r--r-- | dashboard/app/reporting_email.go | 5 | ||||
| -rw-r--r-- | dashboard/app/util_test.go | 8 | ||||
| -rw-r--r-- | dashboard/dashapi/dashapi.go | 1 | ||||
| -rw-r--r-- | pkg/email/parser.go | 11 |
10 files changed, 142 insertions, 143 deletions
diff --git a/dashboard/app/email_test.go b/dashboard/app/email_test.go index 4cb254854..07a242e22 100644 --- a/dashboard/app/email_test.go +++ b/dashboard/app/email_test.go @@ -22,7 +22,7 @@ func TestEmailReport(t *testing.T) { c.client2.UploadBuild(build) crash := testCrash(build, 1) - crash.Maintainers = []string{`"Foo Bar" <foo@bar.com>`, `bar@foo.com`} + crash.Maintainers = []string{`"Foo Bar" <foo@bar.com>`, `bar@foo.com`, `idont@want.EMAILS`} c.client2.ReportCrash(crash) // Report the crash over email and check all fields. @@ -52,7 +52,7 @@ console output: %[2]v kernel config: %[3]v dashboard link: https://testapp.appspot.com/bug?extid=%[1]v compiler: compiler1 -CC: [bar@foo.com foo@bar.com] +CC: [bar@foo.com foo@bar.com idont@want.EMAILS] Unfortunately, I don't have any reproducer for this crash yet. @@ -109,6 +109,14 @@ For more options, visit https://groups.google.com/d/optout. // We used to extract "#syz fix: exact-commit-title" from it. c.incomingEmail(sender0, body0) + c.incomingEmail(sender0, "I don't want emails", EmailOptFrom(`"idont" <idont@WANT.emails>`)) + c.expectNoEmail() + + // This person sends an email and is listed as a maintainer, but opt-out of emails. + // We should not send anything else to them for this bug. Also don't warn about no mailing list in CC. + c.incomingEmail(sender0, "#syz uncc", EmailOptFrom(`"IDONT" <Idont@want.emails>`), EmailOptCC(nil)) + c.expectNoEmail() + // Now report syz reproducer and check updated email. build2 := testBuild(10) build2.Arch = "386" @@ -310,8 +318,7 @@ unknown command "bad-command" // Now mark the bug as fixed. c.incomingEmail(sender1, "#syz fix: some: commit title") - c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 0) + c.expectNoEmail() // Check that the commit is now passed to builders. builderPollResp, _ := c.client2.BuilderPoll(build.Manager) @@ -361,9 +368,6 @@ Content-Type: text/plain #syz upstream `, sender) c.expectOK(c.POST("/_ah/mail/", incoming1)) - - c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 0) } // Basic dup scenario: mark one bug as dup of another. @@ -383,24 +387,25 @@ func TestEmailDup(t *testing.T) { c.client2.ReportCrash(crash2) c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 2) - msg1 := <-c.emailSink - msg2 := <-c.emailSink + msg1 := c.pollEmailBug() + msg2 := c.pollEmailBug() // Dup crash2 to crash1. c.incomingEmail(msg2.Sender, "#syz dup: BUG: slightly more elaborate title") - c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 0) + c.expectNoEmail() // Second crash happens again crash2.ReproC = []byte("int main() {}") c.client2.ReportCrash(crash2) - c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 0) + c.expectNoEmail() // Now close the original bug, and check that new bugs for dup are now created. c.incomingEmail(msg1.Sender, "#syz invalid") + // uncc command must not trugger error reply even for closed bug. + c.incomingEmail(msg1.Sender, "#syz uncc", EmailOptCC(nil)) + c.expectNoEmail() + // New crash must produce new bug in the first reporting. c.client2.ReportCrash(crash2) { @@ -425,25 +430,21 @@ func TestEmailUndup(t *testing.T) { c.client2.ReportCrash(crash2) c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 2) - msg1 := <-c.emailSink - msg2 := <-c.emailSink + msg1 := c.pollEmailBug() + msg2 := c.pollEmailBug() // Dup crash2 to crash1. c.incomingEmail(msg2.Sender, "#syz dup: BUG: slightly more elaborate title") - c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 0) + c.expectNoEmail() // Undup crash2. c.incomingEmail(msg2.Sender, "#syz undup") - c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 0) + c.expectNoEmail() // Now close the original bug, and check that new crashes for the dup does not create bugs. c.incomingEmail(msg1.Sender, "#syz invalid") c.client2.ReportCrash(crash2) - c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 0) + c.expectNoEmail() } func TestEmailCrossReportingDup(t *testing.T) { @@ -491,13 +492,9 @@ func TestEmailCrossReportingDup(t *testing.T) { c.incomingEmail(bugSender, "#syz dup: "+crash2.Title) if test.result { - if len(c.emailSink) != 0 { - msg := <-c.emailSink - t.Fatalf("unexpected reply: %s\n%s\n", msg.Subject, msg.Body) - } + c.expectNoEmail() } else { - c.expectEQ(len(c.emailSink), 1) - msg := <-c.emailSink + msg := c.pollEmailBug() if !strings.Contains(msg.Body, "> #syz dup:") || !strings.Contains(msg.Body, "Can't dup bug to a bug in different reporting") { c.t.Fatalf("bad reply body:\n%v", msg.Body) @@ -512,8 +509,7 @@ func TestEmailErrors(t *testing.T) { // No reply for email without bug hash and no commands. c.incomingEmail("syzbot@testapp.appspotmail.com", "Investment Proposal") - c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 0) + c.expectNoEmail() // If email contains a command we need to reply. c.incomingEmail("syzbot@testapp.appspotmail.com", "#syz invalid") diff --git a/dashboard/app/entities.go b/dashboard/app/entities.go index 32cf37926..a3ab5c79b 100644 --- a/dashboard/app/entities.go +++ b/dashboard/app/entities.go @@ -89,6 +89,7 @@ type Bug struct { CommitInfo []Commit // additional info for commits (for historical reasons parallel array to Commits) HappenedOn []string `datastore:",noindex"` // list of managers PatchedOn []string `datastore:",noindex"` // list of managers + UNCC []string // don't CC these emails on this bug } type Commit struct { diff --git a/dashboard/app/jobs.go b/dashboard/app/jobs.go index 1bb67a991..7bff92b85 100644 --- a/dashboard/app/jobs.go +++ b/dashboard/app/jobs.go @@ -572,10 +572,6 @@ func createBugReportForJob(c context.Context, job *Job, jobKey *datastore.Key, c if err != nil { return nil, err } - kernelConfig, _, err := getText(c, textKernelConfig, build.KernelConfig) - if err != nil { - return nil, err - } bugKey := jobKey.Parent() crashKey := datastore.NewKey(c, "Crash", "", job.CrashID, bugKey) crash := new(Crash) @@ -590,10 +586,6 @@ func createBugReportForJob(c context.Context, job *Job, jobKey *datastore.Key, c if bugReporting == nil { return nil, fmt.Errorf("job bug has no reporting %q", job.Reporting) } - creditEmail, err := email.AddAddrContext(ownEmail(c), bugReporting.ID) - if err != nil { - return nil, err - } var typ dashapi.ReportType switch job.Type { case JobTestPatch: @@ -606,39 +598,21 @@ func createBugReportForJob(c context.Context, job *Job, jobKey *datastore.Key, c return nil, fmt.Errorf("unknown job type %v", job.Type) } rep := &dashapi.BugReport{ - Type: typ, - Namespace: job.Namespace, - Config: reportingConfig, - ID: bugReporting.ID, - JobID: extJobID(jobKey), - ExtID: job.ExtID, - Title: bug.displayTitle(), - Link: fmt.Sprintf("%v/bug?extid=%v", appURL(c), bugReporting.ID), - CreditEmail: creditEmail, - CC: job.CC, - Log: crashLog, - LogLink: externalLink(c, textCrashLog, job.CrashLog), - Report: report, - ReportLink: externalLink(c, textCrashReport, job.CrashReport), - OS: build.OS, - Arch: build.Arch, - VMArch: build.VMArch, - UserSpaceArch: kernelArch(build.Arch), - CompilerID: build.CompilerID, - KernelRepo: build.KernelRepo, - KernelRepoAlias: kernelRepoInfo(build).Alias, - KernelBranch: build.KernelBranch, - KernelCommit: build.KernelCommit, - KernelCommitTitle: build.KernelCommitTitle, - KernelCommitDate: build.KernelCommitDate, - KernelConfig: kernelConfig, - KernelConfigLink: externalLink(c, textKernelConfig, build.KernelConfig), - ReproCLink: externalLink(c, textReproC, crash.ReproC), - ReproSyzLink: externalLink(c, textReproSyz, crash.ReproSyz), - CrashTitle: job.CrashTitle, - Error: jobError, - ErrorLink: externalLink(c, textError, job.Error), - PatchLink: externalLink(c, textPatch, job.Patch), + Type: typ, + Config: reportingConfig, + JobID: extJobID(jobKey), + ExtID: job.ExtID, + CC: job.CC, + Log: crashLog, + LogLink: externalLink(c, textCrashLog, job.CrashLog), + Report: report, + ReportLink: externalLink(c, textCrashReport, job.CrashReport), + ReproCLink: externalLink(c, textReproC, crash.ReproC), + ReproSyzLink: externalLink(c, textReproSyz, crash.ReproSyz), + CrashTitle: job.CrashTitle, + Error: jobError, + ErrorLink: externalLink(c, textError, job.Error), + PatchLink: externalLink(c, textPatch, job.Patch), } if job.Type == JobBisectCause || job.Type == JobBisectFix { kernelRepo := kernelRepoInfo(build) @@ -659,6 +633,9 @@ func createBugReportForJob(c context.Context, job *Job, jobKey *datastore.Key, c rep.Error = rep.Error[len(rep.Error)-maxInlineError:] rep.ErrorTruncated = true } + if err := fillBugReport(c, rep, bug, bugReporting, build); err != nil { + return nil, err + } return rep, nil } diff --git a/dashboard/app/jobs_test.go b/dashboard/app/jobs_test.go index a5783b63b..73e324733 100644 --- a/dashboard/app/jobs_test.go +++ b/dashboard/app/jobs_test.go @@ -74,8 +74,7 @@ func TestJob(t *testing.T) { c.incomingEmail(sender, "#syz test: git://git.git/git.git kernel-branch\n"+patch, EmailOptFrom("\"foo\" <blAcklisteD@dOmain.COM>")) - c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 0) + c.expectNoEmail() pollResp := c.client2.pollJobs(build.Manager) c.expectEQ(pollResp.ID, "") @@ -83,15 +82,13 @@ func TestJob(t *testing.T) { c.incomingEmail(sender, "#syz test: git://git.git/git.git kernel-branch\n"+patch, EmailOptMessageID(1), EmailOptFrom("test@requester.com"), EmailOptCC([]string{"somebody@else.com"})) - c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 0) + c.expectNoEmail() // A dup of the same request with the same Message-ID. c.incomingEmail(sender, "#syz test: git://git.git/git.git kernel-branch\n"+patch, EmailOptMessageID(1), EmailOptFrom("test@requester.com"), EmailOptCC([]string{"somebody@else.com"})) - c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 0) + c.expectNoEmail() pollResp = c.client2.pollJobs("foobar") c.expectEQ(pollResp.ID, "") diff --git a/dashboard/app/notifications_test.go b/dashboard/app/notifications_test.go index d59465e57..12e9aae73 100644 --- a/dashboard/app/notifications_test.go +++ b/dashboard/app/notifications_test.go @@ -27,8 +27,7 @@ func TestEmailNotifUpstreamEmbargo(t *testing.T) { // Upstreaming happens after 14 days, so no emails yet. c.advanceTime(13 * 24 * time.Hour) - c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 0) + c.expectNoEmail() // Now we should get notification about upstreaming and upstream report: c.advanceTime(2 * 24 * time.Hour) @@ -54,8 +53,7 @@ func TestEmailNotifUpstreamSkip(t *testing.T) { c.expectEQ(report.To, []string{"test@syzkaller.com"}) // No emails yet. - c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 0) + c.expectNoEmail() // Now upload repro and it should be auto-upstreamed. crash.ReproOpts = []byte("repro opts") @@ -82,16 +80,13 @@ func TestEmailNotifBadFix(t *testing.T) { c.expectEQ(report.To, []string{"test@syzkaller.com"}) c.incomingEmail(report.Sender, "#syz fix: some: commit title") - c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 0) + c.expectNoEmail() // Notification about bad fixing commit should be send after 90 days. c.advanceTime(50 * 24 * time.Hour) - c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 0) + c.expectNoEmail() c.advanceTime(35 * 24 * time.Hour) - c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 0) + c.expectNoEmail() c.advanceTime(10 * 24 * time.Hour) notif := c.pollEmailBug() if !strings.Contains(notif.Body, "This bug is marked as fixed by commit:\nsome: commit title\n") { @@ -99,8 +94,7 @@ func TestEmailNotifBadFix(t *testing.T) { } // No notifications for another 14 days, then another one. c.advanceTime(13 * 24 * time.Hour) - c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 0) + c.expectNoEmail() c.advanceTime(2 * 24 * time.Hour) notif = c.pollEmailBug() if !strings.Contains(notif.Body, "This bug is marked as fixed by commit:\nsome: commit title\n") { @@ -127,13 +121,11 @@ func TestEmailNotifObsoleted(t *testing.T) { // Bug is open, new crashes don't create new bug. c.client2.ReportCrash(crash) - c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 0) + c.expectNoEmail() // Not yet. c.advanceTime(179 * 24 * time.Hour) - c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 0) + c.expectNoEmail() // Now! c.advanceTime(2 * 24 * time.Hour) @@ -199,8 +191,7 @@ func TestEmailNotifNotObsoleted(t *testing.T) { report4 = c.pollEmailBug() c.advanceTime(179 * 24 * time.Hour) - c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 0) + c.expectNoEmail() c.client2.ReportCrash(crash2) c.incomingEmail(report3.Sender, "I am looking at it") @@ -209,8 +200,7 @@ func TestEmailNotifNotObsoleted(t *testing.T) { // Only crash 4 is obsoleted. notif := c.pollEmailBug() c.expectEQ(notif.Sender, report4.Sender) - c.expectOK(c.GET("/email_poll")) - c.expectEQ(len(c.emailSink), 0) + c.expectNoEmail() // Crash 3 also obsoleted after some time. c.advanceTime(20 * 24 * time.Hour) diff --git a/dashboard/app/reporting.go b/dashboard/app/reporting.go index 1467f63f6..31306b53a 100644 --- a/dashboard/app/reporting.go +++ b/dashboard/app/reporting.go @@ -343,14 +343,6 @@ func createBugReport(c context.Context, bug *Bug, crash *Crash, crashKey *datast if err != nil { return nil, err } - kernelConfig, _, err := getText(c, textKernelConfig, build.KernelConfig) - if err != nil { - return nil, err - } - creditEmail, err := email.AddAddrContext(ownEmail(c), bugReporting.ID) - if err != nil { - return nil, err - } typ := dashapi.ReportNew if !bugReporting.Reported.IsZero() { typ = dashapi.ReportRepro @@ -358,41 +350,23 @@ func createBugReport(c context.Context, bug *Bug, crash *Crash, crashKey *datast kernelRepo := kernelRepoInfo(build) rep := &dashapi.BugReport{ - Type: typ, - Namespace: bug.Namespace, - Config: reportingConfig, - ID: bugReporting.ID, - ExtID: bugReporting.ExtID, - First: bugReporting.Reported.IsZero(), - Moderation: reporting.moderation, - Title: bug.displayTitle(), - Link: fmt.Sprintf("%v/bug?extid=%v", appURL(c), bugReporting.ID), - CreditEmail: creditEmail, - Log: crashLog, - LogLink: externalLink(c, textCrashLog, crash.Log), - Report: report, - ReportLink: externalLink(c, textCrashReport, crash.Report), - Maintainers: append(crash.Maintainers, kernelRepo.CC...), - OS: build.OS, - Arch: build.Arch, - VMArch: build.VMArch, - UserSpaceArch: kernelArch(build.Arch), - CompilerID: build.CompilerID, - KernelRepo: build.KernelRepo, - KernelRepoAlias: kernelRepo.Alias, - KernelBranch: build.KernelBranch, - KernelCommit: build.KernelCommit, - KernelCommitTitle: build.KernelCommitTitle, - KernelCommitDate: build.KernelCommitDate, - KernelConfig: kernelConfig, - KernelConfigLink: externalLink(c, textKernelConfig, build.KernelConfig), - ReproC: reproC, - ReproCLink: externalLink(c, textReproC, crash.ReproC), - ReproSyz: reproSyz, - ReproSyzLink: externalLink(c, textReproSyz, crash.ReproSyz), - CrashID: crashKey.IntID(), - NumCrashes: bug.NumCrashes, - HappenedOn: managersToRepos(c, bug.Namespace, bug.HappenedOn), + Type: typ, + Config: reportingConfig, + ExtID: bugReporting.ExtID, + First: bugReporting.Reported.IsZero(), + Moderation: reporting.moderation, + Log: crashLog, + LogLink: externalLink(c, textCrashLog, crash.Log), + Report: report, + ReportLink: externalLink(c, textCrashReport, crash.Report), + Maintainers: append(crash.Maintainers, kernelRepo.CC...), + ReproC: reproC, + ReproCLink: externalLink(c, textReproC, crash.ReproC), + ReproSyz: reproSyz, + ReproSyzLink: externalLink(c, textReproSyz, crash.ReproSyz), + CrashID: crashKey.IntID(), + NumCrashes: bug.NumCrashes, + HappenedOn: managersToRepos(c, bug.Namespace, bug.HappenedOn), } if bugReporting.CC != "" { rep.CC = strings.Split(bugReporting.CC, "|") @@ -400,9 +374,48 @@ func createBugReport(c context.Context, bug *Bug, crash *Crash, crashKey *datast if bug.BisectCause == BisectYes { rep.BisectCause = bisectFromJob(c, rep, job) } + if err := fillBugReport(c, rep, bug, bugReporting, build); err != nil { + return nil, err + } return rep, nil } +// fillBugReport fills common report fields for bug and job reports. +func fillBugReport(c context.Context, rep *dashapi.BugReport, bug *Bug, bugReporting *BugReporting, + build *Build) error { + kernelConfig, _, err := getText(c, textKernelConfig, build.KernelConfig) + if err != nil { + return err + } + creditEmail, err := email.AddAddrContext(ownEmail(c), bugReporting.ID) + if err != nil { + return err + } + rep.Namespace = bug.Namespace + rep.ID = bugReporting.ID + rep.Title = bug.displayTitle() + rep.Link = fmt.Sprintf("%v/bug?extid=%v", appURL(c), bugReporting.ID) + rep.CreditEmail = creditEmail + rep.OS = build.OS + rep.Arch = build.Arch + rep.VMArch = build.VMArch + rep.UserSpaceArch = kernelArch(build.Arch) + rep.CompilerID = build.CompilerID + rep.KernelRepo = build.KernelRepo + rep.KernelRepoAlias = kernelRepoInfo(build).Alias + rep.KernelBranch = build.KernelBranch + rep.KernelCommit = build.KernelCommit + rep.KernelCommitTitle = build.KernelCommitTitle + rep.KernelCommitDate = build.KernelCommitDate + rep.KernelConfig = kernelConfig + rep.KernelConfigLink = externalLink(c, textKernelConfig, build.KernelConfig) + for _, addr := range bug.UNCC { + rep.CC = email.RemoveFromEmailList(rep.CC, addr) + rep.Maintainers = email.RemoveFromEmailList(rep.Maintainers, addr) + } + return nil +} + func loadBisectJob(c context.Context, bug *Bug) (*Job, *Crash, *datastore.Key, *datastore.Key, error) { bugKey := bug.key(c) var jobs []*Job @@ -679,7 +692,7 @@ func incomingCommandTx(c context.Context, now time.Time, cmd *dashapi.BugUpdate, if bugReporting.Link == "" { bugReporting.Link = cmd.Link } - if len(cmd.CC) != 0 { + if len(cmd.CC) != 0 && cmd.Status != dashapi.BugStatusUnCC { merged := email.MergeEmailLists(strings.Split(bugReporting.CC, "|"), cmd.CC) bugReporting.CC = strings.Join(merged, "|") } @@ -755,6 +768,8 @@ func incomingCommandCmd(c context.Context, now time.Time, cmd *dashapi.BugUpdate bug.DupOf = dupHash case dashapi.BugStatusUpdate: // Just update Link, Commits, etc below. + case dashapi.BugStatusUnCC: + bug.UNCC = email.MergeEmailLists(bug.UNCC, cmd.CC) default: return false, internalError, fmt.Errorf("unknown bug status %v", cmd.Status) } diff --git a/dashboard/app/reporting_email.go b/dashboard/app/reporting_email.go index 7d06eeae3..d05b0cd59 100644 --- a/dashboard/app/reporting_email.go +++ b/dashboard/app/reporting_email.go @@ -326,6 +326,9 @@ func incomingMail(c context.Context, r *http.Request) error { } cmd.Status = dashapi.BugStatusDup cmd.DupOf = msg.CommandArgs + case "uncc", "uncc:": + cmd.Status = dashapi.BugStatusUnCC + cmd.CC = []string{email.CanonicalEmail(msg.From)} default: return replyTo(c, msg, fmt.Sprintf("unknown command %q", msg.Command), nil) } @@ -336,7 +339,7 @@ func incomingMail(c context.Context, r *http.Request) error { if !ok && reply != "" { return replyTo(c, msg, reply, nil) } - if !mailingListInCC && msg.Command != "" { + if !mailingListInCC && msg.Command != "" && cmd.Status != dashapi.BugStatusUnCC { warnMailingListInCC(c, msg, mailingList) } return nil diff --git a/dashboard/app/util_test.go b/dashboard/app/util_test.go index 74540d5b1..fbe11b017 100644 --- a/dashboard/app/util_test.go +++ b/dashboard/app/util_test.go @@ -252,6 +252,14 @@ func (c *Ctx) pollEmailBug() *aemail.Message { return <-c.emailSink } +func (c *Ctx) expectNoEmail() { + c.expectOK(c.GET("/email_poll")) + if len(c.emailSink) != 0 { + msg := <-c.emailSink + c.t.Fatalf("\n%v: got expected email: %v\n%s", caller(0), msg.Subject, msg.Body) + } +} + type apiClient struct { *Ctx *dashapi.Dashboard diff --git a/dashboard/dashapi/dashapi.go b/dashboard/dashapi/dashapi.go index 42200eb15..d53786476 100644 --- a/dashboard/dashapi/dashapi.go +++ b/dashboard/dashapi/dashapi.go @@ -469,6 +469,7 @@ const ( BugStatusInvalid BugStatusDup BugStatusUpdate // aux info update (i.e. ExtID/Link/CC) + BugStatusUnCC // don't CC sender on any future communication ) const ( diff --git a/pkg/email/parser.go b/pkg/email/parser.go index 1b2dbc747..ce652c5da 100644 --- a/pkg/email/parser.go +++ b/pkg/email/parser.go @@ -330,3 +330,14 @@ func MergeEmailLists(lists ...[]string) []string { } return result } + +func RemoveFromEmailList(list []string, toRemove string) []string { + var result []string + toRemove = CanonicalEmail(toRemove) + for _, email := range list { + if CanonicalEmail(email) != toRemove { + result = append(result, email) + } + } + return result +} |
