Skip to content

Commit

Permalink
New rule-based event filtering.
Browse files Browse the repository at this point in the history
- Multiple event rules per bot.
- Filtering on team project, repository and build definition namee.
- XSD to get intellisense in app.config.
- Event handler refactoring - common base class.
  • Loading branch information
kria committed Sep 7, 2014
1 parent 9185cbf commit 5e805a6
Show file tree
Hide file tree
Showing 19 changed files with 415 additions and 92 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ Tfs2Slack is a plugin for Team Foundation Server 2013 that sends notifications t
## Features and TODO

- [x] Notify multiple Slack organizations and channels
- [x] Event type filtering
- [x] Rule-based event filtering
- [x] Project/repository regex filtering
- [x] Configurable notification format
- [x] Link to events in TFS
- [ ] Team project/repository filtering
- [x] Notification links to event in TFS web

### Events

- [x] Build completion
- [x] Work item update
- [x] Team project creation/deletion
- Git
- [x] Push
Expand All @@ -23,8 +24,7 @@ Tfs2Slack is a plugin for Team Foundation Server 2013 that sends notifications t
- [x] Force-push
- TFVC
- [x] Checkin
- [ ] Label
- [ ] Work items
- [ ] Code reviews

## Screenshot

Expand All @@ -42,8 +42,8 @@ Install the Tfs2Slack plugin by dropping `DevCore.Tfs2Slack.dll`, `DevCore.Tfs2S

## TFS 2013 version support

Because of a breaking API change in TFS 2013 Update 2, the plugin as is only works for TFS 2013 update 2 (and possibly later).
I will publish a branch with support for previous TFS 2013 versions at a later date, and also try it on update 3.
Because of a breaking API change in TFS 2013 Update 2, the plugin as is only works for TFS 2013 update 2 and 3 (and possibly later).
I will publish a branch with support for previous TFS 2013 versions at a later date.

## License

Expand Down
8 changes: 5 additions & 3 deletions Tfs2Slack/Configuration/BotElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,12 @@ public string SlackColor
get { return (string)this["slackColor"]; }
}

[ConfigurationProperty("notifyOn")]
public TfsEvents NotifyOn
[ConfigurationProperty("eventRules", IsDefaultCollection = false)]
[ConfigurationCollection(typeof(EventRuleCollection),
AddItemName = "rule")]
public EventRuleCollection EventRules
{
get { return (TfsEvents)this["notifyOn"]; }
get { return (EventRuleCollection)base["eventRules"]; }
}
}
}
2 changes: 2 additions & 0 deletions Tfs2Slack/Configuration/BotElementCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ protected override ConfigurationElement CreateNewElement()
{
return new BotElement();
}

protected override object GetElementKey(ConfigurationElement element)
{
return ((BotElement)element).Name;
}

public BotElement this[int index]
{
get { return (BotElement)BaseGet(index); }
Expand Down
50 changes: 50 additions & 0 deletions Tfs2Slack/Configuration/EventRuleCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Tfs2Slack - http://github.com/kria/Tfs2Slack
*
* Copyright (C) 2014 Kristian Adrup
*
* This file is part of Tfs2Slack.
*
* Tfs2Slack is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version. See included file COPYING for details.
*/

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DevCore.Tfs2Slack.Configuration
{
public class EventRuleCollection : ConfigurationElementCollection,
IEnumerable<EventRuleElement>
{
protected override ConfigurationElement CreateNewElement()
{
return new EventRuleElement();
}

protected override object GetElementKey(ConfigurationElement element)
{
return element;
}
public EventRuleElement this[int index]
{
get { return (EventRuleElement)BaseGet(index); }
}

public new IEnumerator<EventRuleElement> GetEnumerator()
{
int count = base.Count;
for (int i = 0; i < count; i++)
{
yield return base.BaseGet(i) as EventRuleElement;
}
}

}
}
55 changes: 55 additions & 0 deletions Tfs2Slack/Configuration/EventRuleElement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Tfs2Slack - http://github.com/kria/Tfs2Slack
*
* Copyright (C) 2014 Kristian Adrup
*
* This file is part of Tfs2Slack.
*
* Tfs2Slack is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version. See included file COPYING for details.
*/

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DevCore.Tfs2Slack.Configuration
{
public class EventRuleElement : ConfigurationElement
{
[ConfigurationProperty("events", IsRequired = true)]
public TfsEvents Events
{
get { return (TfsEvents)this["events"]; }
}

[ConfigurationProperty("notify")]
public bool Notify
{
get { return (bool)this["notify"]; }
}

[ConfigurationProperty("teamProject")]
public string TeamProject
{
get { return (string)this["teamProject"]; }
}

[ConfigurationProperty("gitRepository")]
public string GitRepository
{
get { return (string)this["gitRepository"]; }
}

[ConfigurationProperty("buildDefinition")]
public string BuildDefinition
{
get { return (string)this["buildDefinition"]; }
}
}
}
30 changes: 15 additions & 15 deletions Tfs2Slack/Configuration/TextElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,34 +100,34 @@ public string CheckinFormat
get { return (string)this["checkinFormat"]; }
}

[ConfigurationProperty("countAddFormat")]
public string CountAddFormat
[ConfigurationProperty("changeCountAddFormat")]
public string ChangeCountAddFormat
{
get { return (string)this["countAddFormat"]; }
get { return (string)this["changeCountAddFormat"]; }
}

[ConfigurationProperty("countDeleteFormat")]
public string CountDeleteFormat
[ConfigurationProperty("changeCountDeleteFormat")]
public string ChangeCountDeleteFormat
{
get { return (string)this["countDeleteFormat"]; }
get { return (string)this["changeCountDeleteFormat"]; }
}

[ConfigurationProperty("countEditFormat")]
public string CountEditFormat
[ConfigurationProperty("changeCountEditFormat")]
public string ChangeCountEditFormat
{
get { return (string)this["countEditFormat"]; }
get { return (string)this["changeCountEditFormat"]; }
}

[ConfigurationProperty("countRenameFormat")]
public string CountRenameFormat
[ConfigurationProperty("changeCountRenameFormat")]
public string ChangeCountRenameFormat
{
get { return (string)this["countRenameFormat"]; }
get { return (string)this["changeCountRenameFormat"]; }
}

[ConfigurationProperty("countSourceRenameFormat")]
public string CountSourceRenameFormat
[ConfigurationProperty("changeCountSourceRenameFormat")]
public string ChangeCountSourceRenameFormat
{
get { return (string)this["countSourceRenameFormat"]; }
get { return (string)this["changeCountSourceRenameFormat"]; }
}

[ConfigurationProperty("workItemchangedFormat")]
Expand Down
18 changes: 18 additions & 0 deletions Tfs2Slack/Configuration/Tfs2SlackSection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,23 @@ public TextElement Text
get { return (TextElement)this["text"]; }
}

[ConfigurationProperty("xmlns")]
public string Xmlns
{
get { return (string)this["xmlns"]; }
}

[ConfigurationProperty("xmlns:xsi")]
public string XmlnsXsi
{
get { return (string)this["xmlns:xsi"]; }
}

[ConfigurationProperty("xsi:noNamespaceSchemaLocation")]
public string XsiNoNamespaceSchemaLocation
{
get { return (string)this["xsi:noNamespaceSchemaLocation"]; }
}

}
}
47 changes: 47 additions & 0 deletions Tfs2Slack/EventHandlers/BaseHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Tfs2Slack - http://github.com/kria/Tfs2Slack
*
* Copyright (C) 2014 Kristian Adrup
*
* This file is part of Tfs2Slack.
*
* Tfs2Slack is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version. See included file COPYING for details.
*/

using Microsoft.TeamFoundation.Framework.Server;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DevCore.Tfs2Slack.EventHandlers
{
public abstract class BaseHandler : IEventHandler
{
protected static Configuration.SettingsElement settings = Configuration.Tfs2SlackSection.Instance.Settings;
protected static Configuration.TextElement text = Configuration.Tfs2SlackSection.Instance.Text;

protected abstract IList<string> _ProcessEvent(TeamFoundationRequestContext requestContext, object notificationEventArgs, Configuration.BotElement bot);

public IList<string> ProcessEvent(TeamFoundationRequestContext requestContext, object notificationEventArgs, Configuration.BotElement bot)
{
IList<string> lines = _ProcessEvent(requestContext, notificationEventArgs, bot);
if (lines != null && lines.Count > 0)
{
IList<string> sendLines = lines;
if (lines != null && lines.Count > settings.MaxLines)
{
int supressedLines = lines.Count - settings.MaxLines;
lines = lines.Take(settings.MaxLines).ToList();
lines.Add(text.LinesSupressedFormat.FormatWith(new { Count = supressedLines }));
}
}

return lines;
}
}
}
32 changes: 23 additions & 9 deletions Tfs2Slack/EventHandlers/BuildCompletionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,20 @@
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;

namespace DevCore.Tfs2Slack.EventHandlers
{
class BuildCompletionHandler : IEventHandler
class BuildCompletionHandler : BaseHandler
{
private static Configuration.SettingsElement settings = Configuration.Tfs2SlackSection.Instance.Settings;
private static Configuration.TextElement text = Configuration.Tfs2SlackSection.Instance.Text;

public IList<string> ProcessEvent(TeamFoundationRequestContext requestContext, object notificationEventArgs, Configuration.BotElement bot)
protected override IList<string> _ProcessEvent(TeamFoundationRequestContext requestContext, object notificationEventArgs, Configuration.BotElement bot)
{
var buildNotification = (BuildCompletionNotificationEvent)notificationEventArgs;
var lines = new List<string>();

var build = buildNotification.Build;

var lines = new List<string>();

if (bot.NotifyOn.HasFlag(TfsEvents.BuildSucceeded) && build.Status.HasFlag(BuildStatus.Succeeded) ||
(bot.NotifyOn.HasFlag(TfsEvents.BuildFailed) && build.Status.HasFlag(BuildStatus.Failed)))
if (IsNotificationMatch(build, bot))
{
var locationService = requestContext.GetService<TeamFoundationLocationService>();

Expand All @@ -57,5 +54,22 @@ public IList<string> ProcessEvent(TeamFoundationRequestContext requestContext, o
return lines;
}

private bool IsNotificationMatch(BuildDetail build, Configuration.BotElement bot)
{
foreach (var rule in bot.EventRules)
{
if (build.Status.HasFlag(BuildStatus.Succeeded) && rule.Events.HasFlag(TfsEvents.BuildSucceeded)
|| build.Status.HasFlag(BuildStatus.Failed) && rule.Events.HasFlag(TfsEvents.BuildFailed))
{
if ((String.IsNullOrEmpty(rule.TeamProject) || Regex.IsMatch(build.TeamProject, rule.TeamProject))
&& (String.IsNullOrEmpty(rule.BuildDefinition) || Regex.IsMatch(build.BuildNumber, rule.BuildDefinition)))
{
return rule.Notify;
}
}
}
return false;
}

}
}
21 changes: 15 additions & 6 deletions Tfs2Slack/EventHandlers/CheckinHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,11 @@

namespace DevCore.Tfs2Slack.EventHandlers
{
class CheckinHandler : IEventHandler
class CheckinHandler : BaseHandler
{
private static Configuration.SettingsElement settings = Configuration.Tfs2SlackSection.Instance.Settings;
private static Configuration.TextElement text = Configuration.Tfs2SlackSection.Instance.Text;

public IList<string> ProcessEvent(TeamFoundationRequestContext requestContext, object notificationEventArgs, Configuration.BotElement bot)
protected override IList<string> _ProcessEvent(TeamFoundationRequestContext requestContext, object notificationEventArgs, Configuration.BotElement bot)
{
var checkin = (CheckinNotification)notificationEventArgs;
if (!bot.NotifyOn.HasFlag(TfsEvents.Checkin)) return null;

string userName = checkin.ChangesetOwner.UniqueName;
if (settings.StripUserDomain) userName = Utils.StripDomain(userName);
Expand All @@ -58,6 +54,9 @@ public IList<string> ProcessEvent(TeamFoundationRequestContext requestContext, o
projectLinks.Add(projectName, String.Format("<{0}|{1}>", projectUrl, projectName));
}
}

if (!IsNotificationMatch(checkin, bot, projectLinks.Keys.ToList())) return null;

string message = text.CheckinFormat.FormatWith(new {
UserName = userName,
ChangesetUrl = changesetUrl,
Expand All @@ -68,5 +67,15 @@ public IList<string> ProcessEvent(TeamFoundationRequestContext requestContext, o

return new string[] { message };
}

public bool IsNotificationMatch(CheckinNotification checkin, Configuration.BotElement bot, IEnumerable<string> projectNames)
{
var rule = bot.EventRules.FirstOrDefault(r => r.Events.HasFlag(TfsEvents.Checkin)
&& (String.IsNullOrEmpty(r.TeamProject) || projectNames.Any(n => Regex.IsMatch(n, r.TeamProject))));

if (rule != null) return rule.Notify;

return false;
}
}
}
Loading

0 comments on commit 5e805a6

Please sign in to comment.