From 0015d3fa4ea59d810a538c526709cc48d8ad1ad9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Mon, 11 Nov 2019 22:42:34 +0000 Subject: [PATCH] feat(ui): replace jira link detection with a generic link finder Fixes #1140 --- cmd/karma/api_test.go | 4 +- cmd/karma/main.go | 16 ++- demo/karma.yaml | 9 +- docs/CONFIGURATION.md | 36 +++--- docs/example.yaml | 9 +- internal/alertmanager/models.go | 4 +- internal/config/config.go | 2 +- internal/config/config_test.go | 5 +- internal/config/models.go | 14 ++- internal/filters/autocomplete_test.go | 25 ++-- ...lence_jira.go => filter_silence_ticket.go} | 18 +-- internal/filters/filter_test.go | 36 +++--- internal/filters/registry.go | 8 +- internal/models/{jira.go => links.go} | 8 +- internal/models/silence.go | 4 +- internal/transform/jira.go | 45 ------- internal/transform/jira_test.go | 96 -------------- internal/transform/links.go | 27 ++++ internal/transform/links_test.go | 118 ++++++++++++++++++ 19 files changed, 256 insertions(+), 228 deletions(-) rename internal/filters/{filter_silence_jira.go => filter_silence_ticket.go} (70%) rename internal/models/{jira.go => links.go} (52%) delete mode 100644 internal/transform/jira.go delete mode 100644 internal/transform/jira_test.go create mode 100644 internal/transform/links.go create mode 100644 internal/transform/links_test.go diff --git a/cmd/karma/api_test.go b/cmd/karma/api_test.go index c9d950c17..bc3eb65e5 100644 --- a/cmd/karma/api_test.go +++ b/cmd/karma/api_test.go @@ -932,8 +932,8 @@ func testAlert(version string, t *testing.T, expectedAlert, gotAlert models.Aler for _, gs := range gotAM.Silences { if es.Comment == gs.Comment && es.CreatedBy == gs.CreatedBy && - es.JiraID == gs.JiraID && - es.JiraURL == gs.JiraURL { + es.TicketID == gs.TicketID && + es.TicketURL == gs.TicketURL { foundSilence = true } } diff --git a/cmd/karma/main.go b/cmd/karma/main.go index 2906cd84a..d3107be46 100644 --- a/cmd/karma/main.go +++ b/cmd/karma/main.go @@ -8,6 +8,7 @@ import ( "os" "os/signal" "path" + "regexp" "strings" "syscall" "time" @@ -197,11 +198,18 @@ func main() { config.Config.LogValues() } - jiraRules := []models.JiraRule{} - for _, rule := range config.Config.JIRA { - jiraRules = append(jiraRules, models.JiraRule{Regex: rule.Regex, URI: rule.URI}) + linkDetectRules := []models.LinkDetectRule{} + for _, rule := range config.Config.Silences.Comments.LinkDetect.Rules { + if rule.Regex == "" || rule.URITemplate == "" { + log.Fatalf("Invalid link detect rule, regex '%s' uriTemplate '%s'", rule.Regex, rule.URITemplate) + } + re, err := regexp.Compile(rule.Regex) + if err != nil { + log.Fatalf("Invalid link detect rule '%s': %s", rule.Regex, err) + } + linkDetectRules = append(linkDetectRules, models.LinkDetectRule{Regex: re, URITemplate: rule.URITemplate}) } - transform.ParseRules(jiraRules) + transform.SetLinkRules(linkDetectRules) apiCache = cache.New(cache.NoExpiration, 10*time.Second) diff --git a/demo/karma.yaml b/demo/karma.yaml index 8a4a99bbe..7b4dab137 100644 --- a/demo/karma.yaml +++ b/demo/karma.yaml @@ -64,9 +64,12 @@ log: sentry: private: https://84a9ef37a6ed4fdb80e9ea2310d1ed26:8c6ee6f0ab02406482ff4b4e824e2c27@sentry.io/1279017 public: https://84a9ef37a6ed4fdb80e9ea2310d1ed26@sentry.io/1279017 -jira: - - regex: DEVOPS-[0-9]+ - uri: https://jira.example.com +silences: + comments: + linkDetect: + rules: + - regex: "(DEVOPS-[0-9]+)" + uriTemplate: https://jira.example.com/browse/$1 silenceForm: author: populate_from_header: diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 3099e35ec..673a11ee1 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -684,30 +684,38 @@ log: format: text ``` -### JIRA +### Silences -`jira` section allows specifying a list of regex rules for finding links to Jira -issues in silence comments. If a string inside a comment matches one of the -rules it will be rendered as a link. +`silences` section allows specifying to configure silence post post-processing. Syntax: ```YAML -jira: - - regex: string - - uri: string +silences: + comments: + linkDetect: + rules: list of link detection rules ``` -- `regex` - regular expression for matching Jira issue ID. -- `uri` - base URL for Jira instance, `/browse/FOO-1` will be appended to it - (where `FOO-1` is example issue ID). +- `comments:linkDetect:rules` allows to specify a list of rules to detect links + inside silence comments. It's intended to find ticket system ID strings and + turn them into links. + Each rule must specify: + - `regex` - regular expression that matches ticket system IDs. Each regex must + contain at least one capture group `(regex)`. + - `uriTemplate` - template string that will be used to generate a link. + Each template must include `$1` which will be replaced with text matched + by the `regex`. Example where a string `DEVOPS-123` inside a comment would be rendered as a link -to `https://jira.example.com/browse/DEVOPS-123`. +to a JIRA ticket `https://jira.example.com/browse/DEVOPS-123`. ```YAML -jira: - - regex: DEVOPS-[0-9]+ - uri: https://jira.example.com +silences: + comments: + linkDetect: + rules: + - regex: "(DEVOPS-[0-9]+)" + uriTemplate: https://jira.example.com/browse/$1 ``` Defaults: diff --git a/docs/example.yaml b/docs/example.yaml index bfa11ca85..86d89936e 100644 --- a/docs/example.yaml +++ b/docs/example.yaml @@ -46,9 +46,12 @@ listen: log: config: false level: info -jira: - - regex: DEVOPS-[0-9]+ - uri: https://jira.example.com +silences: + comments: + linkDetect: + rules: + - regex: "(DEVOPS-[0-9]+)" + uriTemplate: https://jira.example.com/browse/$1 receivers: keep: [] strip: [] diff --git a/internal/alertmanager/models.go b/internal/alertmanager/models.go index f435cfcb4..21e8807e4 100644 --- a/internal/alertmanager/models.go +++ b/internal/alertmanager/models.go @@ -184,11 +184,11 @@ func (am *Alertmanager) pullSilences(version string) error { } log.Infof("[%s] Got %d silences(s) in %s", am.Name, len(silences), time.Since(start)) - log.Infof("[%s] Detecting JIRA links in silences (%d)", am.Name, len(silences)) + log.Infof("[%s] Detecting ticket links in silences (%d)", am.Name, len(silences)) silenceMap := map[string]models.Silence{} for _, silence := range silences { silence := silence // scopelint pin - silence.JiraID, silence.JiraURL = transform.DetectJIRAs(&silence) + silence.TicketID, silence.TicketURL = transform.DetectLinks(&silence) silenceMap[silence.ID] = silence } diff --git a/internal/config/config.go b/internal/config/config.go index 330a7694b..e7d2abc9d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -218,7 +218,7 @@ func (config *configSchema) Read() { } } - err = v.UnmarshalKey("jira", &config.JIRA) + err = v.UnmarshalKey("silences.comments.linkDetect.rules", &config.Silences.Comments.LinkDetect.Rules) if err != nil { log.Fatal(err) } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index d3adb6300..365874bda 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -123,13 +123,16 @@ log: config: true level: info format: text -jira: [] receivers: keep: [] strip: [] sentry: private: secret key public: public key +silences: + comments: + linkDetect: + rules: [] silenceForm: author: populate_from_header: diff --git a/internal/config/models.go b/internal/config/models.go index 38ebe4099..10bb58db6 100644 --- a/internal/config/models.go +++ b/internal/config/models.go @@ -20,9 +20,9 @@ type alertmanagerConfig struct { Headers map[string]string } -type jiraRule struct { - Regex string - URI string +type LinkDetectRules struct { + Regex string `yaml:"regex" mapstructure:"regex"` + URITemplate string `yaml:"uriTemplate" mapstructure:"uriTemplate"` } type CustomLabelColor struct { @@ -94,7 +94,6 @@ type configSchema struct { Level string Format string } - JIRA []jiraRule Receivers struct { Keep []string Strip []string @@ -103,6 +102,13 @@ type configSchema struct { Private string Public string } + Silences struct { + Comments struct { + LinkDetect struct { + Rules []LinkDetectRules `yaml:"rules" mapstructure:"rules"` + } `yaml:"linkDetect" mapstructure:"linkDetect"` + } `yaml:"comments" mapstructure:"comments"` + } `yaml:"silences" mapstructure:"silences"` SilenceForm struct { Author struct { PopulateFromHeader struct { diff --git a/internal/filters/autocomplete_test.go b/internal/filters/autocomplete_test.go index bf6bd97e4..734b94d94 100644 --- a/internal/filters/autocomplete_test.go +++ b/internal/filters/autocomplete_test.go @@ -8,7 +8,7 @@ import ( "github.com/prymitive/karma/internal/filters" "github.com/prymitive/karma/internal/models" - "github.com/pmezard/go-difflib/difflib" + "github.com/google/go-cmp/cmp" ) type acTest struct { @@ -57,7 +57,7 @@ var acTests = []acTest{ "1234567890": { ID: "1234567890", CreatedBy: "me@example.com", - JiraID: "JIRA-1", + TicketID: "JIRA-1", }, }}, }, @@ -89,10 +89,10 @@ var acTests = []acTest{ "@silence_author=~me@example.com", "@silence_id!=1234567890", "@silence_id=1234567890", - "@silence_jira!=JIRA-1", - "@silence_jira!~JIRA-1", - "@silence_jira=JIRA-1", - "@silence_jira=~JIRA-1", + "@silence_ticket!=JIRA-1", + "@silence_ticket!~JIRA-1", + "@silence_ticket=JIRA-1", + "@silence_ticket=~JIRA-1", "@state!=active", "@state!=suppressed", "@state=active", @@ -131,18 +131,9 @@ func TestBuildAutocomplete(t *testing.T) { expectedJSON, _ := json.Marshal(acTest.Expected) if string(resultJSON) != string(expectedJSON) { - diff := difflib.UnifiedDiff{ - A: difflib.SplitLines(string(expectedJSON)), - B: difflib.SplitLines(string(resultJSON)), - FromFile: "Expected", - ToFile: "Returned", - Context: 3, + if diff := cmp.Diff(expectedJSON, resultJSON); diff != "" { + t.Errorf("Wrong autocomplete data returned (-want +got):\n%s", diff) } - text, err := difflib.GetUnifiedDiffString(diff) - if err != nil { - t.Error(err) - } - t.Errorf("Autocomplete mismatch:\n%s", text) } } } diff --git a/internal/filters/filter_silence_jira.go b/internal/filters/filter_silence_ticket.go similarity index 70% rename from internal/filters/filter_silence_jira.go rename to internal/filters/filter_silence_ticket.go index 6c584b421..99504dac0 100644 --- a/internal/filters/filter_silence_jira.go +++ b/internal/filters/filter_silence_ticket.go @@ -7,11 +7,11 @@ import ( "github.com/prymitive/karma/internal/models" ) -type silenceJiraFilter struct { +type silenceTicketFilter struct { alertFilter } -func (filter *silenceJiraFilter) Match(alert *models.Alert, matches int) bool { +func (filter *silenceTicketFilter) Match(alert *models.Alert, matches int) bool { if filter.IsValid { var isMatch bool if alert.IsSilenced() { @@ -19,7 +19,7 @@ func (filter *silenceJiraFilter) Match(alert *models.Alert, matches int) bool { for _, am := range alert.Alertmanager { silence, found := am.Silences[silenceID] if found { - m := filter.Matcher.Compare(silence.JiraID, filter.Value) + m := filter.Matcher.Compare(silence.TicketID, filter.Value) if m { isMatch = m } @@ -38,26 +38,26 @@ func (filter *silenceJiraFilter) Match(alert *models.Alert, matches int) bool { panic(e) } -func newSilenceJiraFilter() FilterT { - f := silenceJiraFilter{} +func newSilenceTicketFilter() FilterT { + f := silenceTicketFilter{} return &f } -func silenceJiraIDAutocomplete(name string, operators []string, alerts []models.Alert) []models.Autocomplete { +func silenceTicketIDAutocomplete(name string, operators []string, alerts []models.Alert) []models.Autocomplete { tokens := map[string]models.Autocomplete{} for _, alert := range alerts { if alert.IsSilenced() { for _, silenceID := range alert.SilencedBy { for _, am := range alert.Alertmanager { silence, found := am.Silences[silenceID] - if found && silence.JiraID != "" { + if found && silence.TicketID != "" { for _, operator := range operators { - token := fmt.Sprintf("%s%s%s", name, operator, silence.JiraID) + token := fmt.Sprintf("%s%s%s", name, operator, silence.TicketID) tokens[token] = makeAC(token, []string{ name, strings.TrimPrefix(name, "@"), fmt.Sprintf("%s%s", name, operator), - silence.JiraID, + silence.TicketID, }) } } diff --git a/internal/filters/filter_test.go b/internal/filters/filter_test.go index 53fb816e6..9da0d5744 100644 --- a/internal/filters/filter_test.go +++ b/internal/filters/filter_test.go @@ -146,73 +146,73 @@ var tests = []filterTest{ }, { - Expression: "@silence_jira=1", + Expression: "@silence_ticket=1", IsValid: true, Alert: models.Alert{State: "suppressed", SilencedBy: []string{"1"}}, - Silence: models.Silence{ID: "1", JiraID: "1"}, + Silence: models.Silence{ID: "1", TicketID: "1"}, IsMatch: true, }, { - Expression: "@silence_jira=2", + Expression: "@silence_ticket=2", IsValid: true, Alert: models.Alert{State: "suppressed", SilencedBy: []string{"1"}}, Silence: models.Silence{ID: "1"}, IsMatch: false, }, { - Expression: "@silence_jira!=3", + Expression: "@silence_ticket!=3", IsValid: true, Alert: models.Alert{State: "suppressed", SilencedBy: []string{"1"}}, - Silence: models.Silence{ID: "1", JiraID: "x"}, + Silence: models.Silence{ID: "1", TicketID: "x"}, IsMatch: true, }, { - Expression: "@silence_jira!=4", + Expression: "@silence_ticket!=4", IsValid: true, Alert: models.Alert{State: "suppressed", SilencedBy: []string{"1"}}, - Silence: models.Silence{ID: "1", JiraID: "4"}, + Silence: models.Silence{ID: "1", TicketID: "4"}, IsMatch: false, }, { - Expression: "@silence_jira!=5", + Expression: "@silence_ticket!=5", IsValid: true, Alert: models.Alert{State: "suppressed", SilencedBy: []string{"1"}}, Silence: models.Silence{ID: "1"}, IsMatch: true, }, { - Expression: "@silence_jira=~abc", + Expression: "@silence_ticket=~abc", IsValid: true, Alert: models.Alert{State: "suppressed", SilencedBy: []string{"1"}}, - Silence: models.Silence{ID: "1", JiraID: "xxabcxx"}, + Silence: models.Silence{ID: "1", TicketID: "xxabcxx"}, IsMatch: true, }, { - Expression: "@silence_jira=~abc", + Expression: "@silence_ticket=~abc", IsValid: true, Alert: models.Alert{State: "suppressed", SilencedBy: []string{"1"}}, - Silence: models.Silence{ID: "1", JiraID: "xxx"}, + Silence: models.Silence{ID: "1", TicketID: "xxx"}, IsMatch: false, }, { - Expression: "@silence_jira=~", + Expression: "@silence_ticket=~", IsValid: false, Alert: models.Alert{State: "suppressed", SilencedBy: []string{"1"}}, - Silence: models.Silence{ID: "1", JiraID: "xxx"}, + Silence: models.Silence{ID: "1", TicketID: "xxx"}, IsMatch: false, }, { - Expression: "@silence_jira~=", + Expression: "@silence_ticket~=", IsValid: false, Alert: models.Alert{State: "suppressed", SilencedBy: []string{"1"}}, - Silence: models.Silence{ID: "1", JiraID: "xxx"}, + Silence: models.Silence{ID: "1", TicketID: "xxx"}, IsMatch: false, }, { - Expression: "@silence_jira~=1", + Expression: "@silence_ticket~=1", IsValid: false, Alert: models.Alert{State: "suppressed", SilencedBy: []string{"1"}}, - Silence: models.Silence{ID: "1", JiraID: "xxx"}, + Silence: models.Silence{ID: "1", TicketID: "xxx"}, IsMatch: false, }, diff --git a/internal/filters/registry.go b/internal/filters/registry.go index c14a4f11d..4fffdfece 100644 --- a/internal/filters/registry.go +++ b/internal/filters/registry.go @@ -76,11 +76,11 @@ var AllFilters = []filterConfig{ Autocomplete: silenceIDAutocomplete, }, { - Label: "@silence_jira", - LabelRe: regexp.MustCompile("^@silence_jira$"), + Label: "@silence_ticket", + LabelRe: regexp.MustCompile("^@silence_ticket$"), SupportedOperators: []string{regexpOperator, negativeRegexOperator, equalOperator, notEqualOperator}, - Factory: newSilenceJiraFilter, - Autocomplete: silenceJiraIDAutocomplete, + Factory: newSilenceTicketFilter, + Autocomplete: silenceTicketIDAutocomplete, }, { Label: "@silence_author", diff --git a/internal/models/jira.go b/internal/models/links.go similarity index 52% rename from internal/models/jira.go rename to internal/models/links.go index bd02ce178..4f72a4070 100644 --- a/internal/models/jira.go +++ b/internal/models/links.go @@ -1,8 +1,10 @@ package models +import "regexp" + // JiraRule is used to detect JIRA issue IDs in strings and turn those into // links -type JiraRule struct { - Regex string - URI string +type LinkDetectRule struct { + Regex *regexp.Regexp + URITemplate string } diff --git a/internal/models/silence.go b/internal/models/silence.go index 69fa76702..152196b2e 100644 --- a/internal/models/silence.go +++ b/internal/models/silence.go @@ -21,8 +21,8 @@ type Silence struct { CreatedBy string `json:"createdBy"` Comment string `json:"comment"` // karma fields - JiraID string `json:"jiraID"` - JiraURL string `json:"jiraURL"` + TicketID string `json:"ticketID"` + TicketURL string `json:"ticketURL"` } // ManagedSilence is a standalone silence detached from any alert diff --git a/internal/transform/jira.go b/internal/transform/jira.go deleted file mode 100644 index e08af0c9d..000000000 --- a/internal/transform/jira.go +++ /dev/null @@ -1,45 +0,0 @@ -package transform - -import ( - "fmt" - "log" - "regexp" - - "github.com/prymitive/karma/internal/models" -) - -type jiraDetectRule struct { - Regexp *regexp.Regexp - URL string -} - -var jiraDetectRules = []jiraDetectRule{} - -// ParseRules will parse and validate list of JIRA detection rules provided -// from config, valid rules will be stored for future use in DetectJIRAs() calls -func ParseRules(rules []models.JiraRule) { - for _, rule := range rules { - if rule.Regex == "" || rule.URI == "" { - log.Fatalf("Invalid JIRA rule with regexp '%s' and url '%s'", rule.Regex, rule.URI) - } - jdr := jiraDetectRule{ - Regexp: regexp.MustCompile(rule.Regex), - URL: rule.URI, - } - jiraDetectRules = append(jiraDetectRules, jdr) - } -} - -// DetectJIRAs will try to find JIRA links in Alertmanager silence objects -// using regexp rules from configuration that were parsed and populated -// by ParseRules call -func DetectJIRAs(silence *models.Silence) (jiraID, jiraLink string) { - for _, jdr := range jiraDetectRules { - jiraID := jdr.Regexp.FindString(silence.Comment) - if jiraID != "" { - jiraLink := fmt.Sprintf("%s/browse/%s", jdr.URL, jiraID) - return jiraID, jiraLink - } - } - return "", "" -} diff --git a/internal/transform/jira_test.go b/internal/transform/jira_test.go deleted file mode 100644 index 343a327ba..000000000 --- a/internal/transform/jira_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package transform_test - -import ( - "testing" - - "github.com/prymitive/karma/internal/models" - "github.com/prymitive/karma/internal/transform" -) - -type jiraTest struct { - silence models.Silence - jiraID string - jiraLink string -} - -var jiraRules = []models.JiraRule{ - { - Regex: "DEVOPS-[0-9]+", - URI: "https://jira.example.com", - }, - { - Regex: "PROJECT-[0-9]+", - URI: "https://example.com", - }, -} - -var jiraTests = []jiraTest{ - { - silence: models.Silence{ - Comment: "Lorem ipsum dolor sit amet", - }, - }, - { - silence: models.Silence{ - Comment: "DVOPS-123", - }, - }, - { - silence: models.Silence{ - Comment: "DEVOPS team", - }, - }, - { - silence: models.Silence{ - Comment: "a project-1 b", - }, - }, - { - silence: models.Silence{ - Comment: "a PROJECT- b", - }, - }, - { - silence: models.Silence{ - Comment: "DEVOPS-1", - }, - jiraID: "DEVOPS-1", - jiraLink: "https://jira.example.com/browse/DEVOPS-1", - }, - { - silence: models.Silence{ - Comment: "DEVOPS-123", - }, - jiraID: "DEVOPS-123", - jiraLink: "https://jira.example.com/browse/DEVOPS-123", - }, - { - silence: models.Silence{ - Comment: "a DEVOPS-1 b", - }, - jiraID: "DEVOPS-1", - jiraLink: "https://jira.example.com/browse/DEVOPS-1", - }, - { - silence: models.Silence{ - Comment: "PROJECT-9", - }, - jiraID: "PROJECT-9", - jiraLink: "https://example.com/browse/PROJECT-9", - }, -} - -func TestDetectJIRAs(t *testing.T) { - transform.ParseRules(jiraRules) - for _, testCase := range jiraTests { - jiraID, jiraLink := transform.DetectJIRAs(&testCase.silence) - if jiraID != testCase.jiraID { - t.Errorf("Invalid JIRA ID detected in silence comment '%s', expected '%s', got '%s'", - testCase.silence.Comment, testCase.jiraID, jiraID) - } - if jiraID != testCase.jiraID { - t.Errorf("Invalid JIRA link detected in silence comment '%s', expected '%s', got '%s'", - testCase.silence.Comment, testCase.jiraLink, jiraLink) - } - } -} diff --git a/internal/transform/links.go b/internal/transform/links.go new file mode 100644 index 000000000..80fd62dc0 --- /dev/null +++ b/internal/transform/links.go @@ -0,0 +1,27 @@ +package transform + +import ( + "github.com/prymitive/karma/internal/models" +) + +var linkDetectRules []models.LinkDetectRule + +func SetLinkRules(rules []models.LinkDetectRule) { + linkDetectRules = rules +} + +// DetectLinks will try to find all links in Alertmanager silence objects +// using regexp rules from configuration +func DetectLinks(silence *models.Silence) (text, uri string) { + for _, rule := range linkDetectRules { + m := rule.Regex.FindString(silence.Comment) + if m != "" { + result := []byte{} + for _, submatches := range rule.Regex.FindAllStringSubmatchIndex(silence.Comment, -1) { + result = rule.Regex.ExpandString(result, rule.URITemplate, silence.Comment, submatches) + } + return m, string(result) + } + } + return "", "" +} diff --git a/internal/transform/links_test.go b/internal/transform/links_test.go new file mode 100644 index 000000000..b5009f5e7 --- /dev/null +++ b/internal/transform/links_test.go @@ -0,0 +1,118 @@ +package transform_test + +import ( + "regexp" + "testing" + + "github.com/prymitive/karma/internal/config" + "github.com/prymitive/karma/internal/models" + "github.com/prymitive/karma/internal/transform" +) + +type linkTest struct { + silence models.Silence + text string + uri string +} + +var linkRules = []config.LinkDetectRules{ + { + Regex: "(DEVOPS-[0-9]+)", + URITemplate: "https://jira.example.com/browse/$1", + }, + { + Regex: "(PROJECT-[0-9]+)", + URITemplate: "https://example.com/browse/$1", + }, + { + Regex: "(redmine[0-9]+)", + URITemplate: "https://redmine.example.com/issue/$1.php", + }, +} + +var linkTests = []linkTest{ + { + silence: models.Silence{ + Comment: "Lorem ipsum dolor sit amet", + }, + }, + { + silence: models.Silence{ + Comment: "DVOPS-123", + }, + }, + { + silence: models.Silence{ + Comment: "DEVOPS team", + }, + }, + { + silence: models.Silence{ + Comment: "a project-1 b", + }, + }, + { + silence: models.Silence{ + Comment: "a PROJECT- b", + }, + }, + { + silence: models.Silence{ + Comment: "DEVOPS-1", + }, + text: "DEVOPS-1", + uri: "https://jira.example.com/browse/DEVOPS-1", + }, + { + silence: models.Silence{ + Comment: "DEVOPS-123", + }, + text: "DEVOPS-123", + uri: "https://jira.example.com/browse/DEVOPS-123", + }, + { + silence: models.Silence{ + Comment: "a DEVOPS-1 b", + }, + text: "DEVOPS-1", + uri: "https://jira.example.com/browse/DEVOPS-1", + }, + { + silence: models.Silence{ + Comment: "PROJECT-9", + }, + text: "PROJECT-9", + uri: "https://example.com/browse/PROJECT-9", + }, + { + silence: models.Silence{ + Comment: "redmine0", + }, + text: "redmine0", + uri: "https://redmine.example.com/issue/redmine0.php", + }, +} + +func TestDetectTickets(t *testing.T) { + linkDetectRules := []models.LinkDetectRule{} + for _, rule := range linkRules { + re, err := regexp.Compile(rule.Regex) + if err != nil { + t.Errorf("Invalid link detect rule '%s': %s", rule.Regex, err) + } + linkDetectRules = append(linkDetectRules, models.LinkDetectRule{Regex: re, URITemplate: rule.URITemplate}) + } + transform.SetLinkRules(linkDetectRules) + + for _, testCase := range linkTests { + text, uri := transform.DetectLinks(&testCase.silence) + if text != testCase.text { + t.Errorf("Invalid ticket ID detected in silence comment '%s', expected '%s', got '%s'", + testCase.silence.Comment, testCase.text, text) + } + if text != testCase.text { + t.Errorf("Invalid ticket link detected in silence comment '%s', expected '%s', got '%s'", + testCase.silence.Comment, testCase.uri, uri) + } + } +}