Welcome to MSDN Blogs Sign in | Join | Help

Team Foundation: Alerts missing subject line?

Team Foundation creates alerts that notify users and/or web applications of certain events that have occurred. Some of these alerts are used internally by Team Foundation (e.g., notifying another TF component of an important change to group membership data). Most are used by end users to receive notification of work item changes, checkins, and build activitites.

Pete Sheill and others have discussed alerts in the past. Recently, we became aware of a problem that manifests itself through the Team Foundation eventing mechanism which includes alerts.

When the alert subject line is blank, this often means that one of the assemblies used to process alerts prior to sending isn't working properly. In some cases, these assemblies fail to load and an event log message will be written.

There are 3 assemblies that process alerts prior to sending to the subscribers

  • Microsoft.TeamFoundation.Build.Server.EventFilter.dll
  • Microsoft.TeamFoundation.VersionControl.Server.EventFilter.dll
  • Microsoft.TeamFoundation.VersionControl.WorkItemTracking.Server.EventFilter.dll

These assemblies are loaded by the Services web application when it starts. This web application matches events with subscription data created from Visual Studio or using the command line tool BisSubscribe

We've found that these assemblies do not load when the configuration settings for that web service have been changed using the ASP.NET tab of the web site properties. You can reach this by navigating to the Team Foundation web site from inetmgr, selecting Properties and then choosing the ASP.NET tab followed by selecting Edit Configuration.

This causes an attribute to be added to the config element of the corresponding web.config file. E.g., if you alter the Services configuration, installPath\Web Services\Services\Web.config is altered.

Before making the configuration change, the web.config files look like

<configuration>

<!-- Team Foundation/Web application values -->

</configuration>

Following a configuration change using  Edit Properties:

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

<!-- settings -->

</configuration>

To restore behavior, use an editor or Notepad to remove the namespace attribute. Note that you do not need to run iisreset following this change.

The underlying problem is a known issue and is being addressed.

Posted by jefflu | 1 Comments

Error Reporting -- Does anybody review those Watson reports?

I'm part of the group that reviews Watson reports for Team Foundation. On a weekly basis, we meet and review all Watson reports received since the last review period. Each report is first checked to see if it is for a known problem. Many reports are for issues that we have either discovered from internal users or from a previous Watson report. If the issue has not been seen before, then we will gather information and assign a bug to the responsible developers. We also track Watson bugs and make sure that the bug is treated appropriately by the developers.

Watson crashes represent a concrete occurrence of an issue -- sometimes, we find that the issue in a Watson report is not something that we can act on (e.g., a time-out condition) but we can handle better by informing the user or using an alternate algorithm.

We treat problems reported through Watson seriously and I want to assure you that Team Foundation Watson reports that you permit to be sent to Microsoft will be treated appropriately and timely.

Let me know if you have any concerns about sending Watson reports for Team Foundation.

Posted by jefflu | 2 Comments

(Updated) Sample: Version Control RSS Feed

I have updated the sample RSS feed; if you're using Beta 3, you should use the updated feed posted below. The sample was first posted here.

The sample below allows you to subscribe to a feed which generates information for a specific user. Append ?user=domain\alias to the subscription path.

<%@ Page Language="c#" %>
<%@ OutputCache Duration="20" Location="Server" VaryByParam="state" VaryByCustom="minorversion" VaryByHeader="Accept-Language"%>
<%@ Assembly Name="Microsoft.TeamFoundation.Client, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" %>
<%@ Assembly Name="Microsoft.TeamFoundation.VersionControl.Client, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" %>
<%@ Assembly Name="Microsoft.TeamFoundation.VersionControl.Common, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" %>
<%@ Assembly Name="Microsoft.TeamFoundation.VersionControl.Common.Integration, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" %>


<%
// "Copyright © Microsoft Corporation.  All rights reserved.  These Samples are based in part on the Extensible Markup Language (XML) 1.0 (Third Edition) specification Copyright © 2004 W3C® (MIT, ERCIM, Keio. All rights reserved. http://www.w3.org/consortium/legal/2002/copyright-documents-20021231."
%>

<%
//This posting is provided "AS IS" with no warranties of any kind and confers no rights.  Use of samples included in this posting is subject to the terms specified at http://www.microsoft.com/info/cpyright.htm.
%>

<%@ Import Namespace="System" %>
<%@ Import Namespace="System.Collections" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Web" %>
<%@ Import Namespace="System.Xml" %>
<%@ Import Namespace="Microsoft.TeamFoundation.Client" %>
<%@ Import Namespace="Microsoft.TeamFoundation.VersionControl.Client"%>
<%@ Import Namespace="Microsoft.TeamFoundation.VersionControl.Common" %>

<%
// Generate an RSS feed for Team Foundation Source Code Control checkins
//
// Note: Only one request per "pester interval" from each system will be honored. See
//       pesterInterval below.
//
// This feed returns information about the most recent N checkins. See maxCheckinCount
// below.
//
// Invoking this page without any parameters returns information about all Team
// Foundation checkins up to the maximum count. If a filename is supplied, information
// about the most recent checkins for that file only are returned. Specify a filename
// by adding ?serverPath=<serverPath>. Note that the filename must be expressed as a server
// pathname without the leading $/.
//
// E.g., to see the activity for a file $/teamProjectA/myFile, add this to the Url
// ?serverPath=teamProjectA/myfile
//
// This page returns no items if the file does not exist.

// ***************************************************
// (Default: on) Set this to true to throttle user requests by host/username
// ***************************************************
bool throttleRequests = true;

// Ignore requests from the same machine within this interval
int pesterInterval = 30 * 1000; // expressed in milliseconds

int maxSubjectLength = 60;

if (throttleRequests)
{
    // If the originating system has been serviced recently, drop the request
    object lastRequest = Context.Cache[User.Identity.Name+Request.UserHostAddress];
    if (lastRequest != null)
    {
        if ((int) lastRequest + pesterInterval < Environment.TickCount)
        {
            return;
        }
    }
}

// Default -- everything under $/
string serverPathname = VersionControlPath.RootFolder;
string username = null;

try
{
    // Check whether information on a specific file or userwas requested
    string pathname = Request.Params["serverPath"];
    if (pathname != null)
    {
        // The pathname does not contain a leading '$'
        serverPathname = VersionControlPath.Combine(VersionControlPath.RootFolder, pathname);
        // The pathname is valid.
    }
    username = Request.Params["user"];
}
catch
{
    Response.StatusCode = 404;
    Response.End();
}

// The maximum number of changes to return per query
int maxCheckinCount = 50;

string rssVersion = "2.0";
string rssTtl = "5";
string rssLanguage = "en-US";
string rssLink = Request.Url.ToString();
string rssEmptyComment = "None";
string rssTitle = "Team Foundation RSS Feed for Source Code Control Checkins";
string rssGenerator = "Sample RSS Feed Generator for Team Foundation";
string rssDescription = "<p>This feed provides information on Team Foundation Checkins. Each Team Foundation checkin is manifested as a changeset. The changesets that have been created recently are listed here.</p><p>This feed contains the most recent <i>{0}</i> checkin(s).</p>";
string rssItemOnBehalfOf = "(on behalf of {0}) ";
string rssItemTitle = "Check-in {0}: {1}";
string rssItemSubject = "Check-in contains changes to {0} item(s)";
string rssNoItemsAvailableDescription = "An error occurred obtaining the latest checkin information from Team Foundation. Try again later.";
string rssItemDescription = "<p><a href=\"{5}\">Changeset {0}</a> was checked in by <i>{1} {2}</i>on {3}. This checkin includes changes to {4} item(s).</p><p>Checkin comment: {6}</p><p>You can view the details of the checkin by selecting the provided link.</p>";
string exceptionMessage = "<p>An exception occurred while getting updated information from Team Foundation; the detailed exception message is <i>{0}</i></p>";
string itemNotFoundErrorMessage = "<p>There were no items found.</p>";
string accessDeniedErrorMessage = "<p>You do not have permission to obtain the Team Foundation checkin information.</p>";
string otherErrorMessage = "<p>An error occurred. This may be a transient error or a permanent one. Please check the event log for messages from VSTF Source Code Control</p>";

Response.ContentType = "text/xml";
XmlTextWriter xt = new XmlTextWriter(Response.OutputStream, null);
// Begin creating the XML document
xt.WriteStartElement("rss");
xt.WriteAttributeString("version", rssVersion);
xt.WriteStartElement("channel");
xt.WriteElementString("title", rssTitle);
xt.WriteElementString("ttl", rssTtl);
xt.WriteElementString("link", rssLink);
xt.WriteElementString("pubDate", DateTime.Now.ToString());
xt.WriteElementString("language", rssLanguage);
xt.WriteElementString("generator", rssGenerator);
try
{
    RecursionType recursionType = RecursionType.Full;


    // Obtain the list of changes from the mid-tier; the number of changes is reported in
    // the channel description.
    IEnumerable changesetEnum = null;
    try
    {
        TeamFoundationServer Tfs = null;
        object cacheEntry = Context.Cache["TeamFoundationServer"];
        if (cacheEntry == null)
        {
            // Note: only works on localhost
            Tfs = TeamFoundationServerFactory.GetServer("http://localhost:8080");
            Context.Cache["TeamFoundationServer"] = Tfs;
        }
        else
        {
            Tfs = (TeamFoundationServer) cacheEntry;
        }
        VersionControlServer Vcs = (VersionControlServer) Tfs.GetService(typeof(VersionControlServer));
        // Return changes
        changesetEnum = Vcs.QueryHistory(serverPathname,  // on this item
                                         VersionSpec.Latest, // item version
                                         0,               // that are not deleted
                                         recursionType,   // at or below this item
                                         username,         // user
                                         null,            // start version
                                         null,            // stop version
                                         maxCheckinCount, // Up to this many changes
                                         true,            // include changes
                                         false);

        int changeCount = 0;
        foreach (Changeset change in changesetEnum)
        {
            changeCount++;
        }
        xt.WriteElementString("description", String.Format(rssDescription, changeCount));
    }
    catch (Exception e)
    {
        if (e is ItemNotFoundException)
        {
            rssNoItemsAvailableDescription += itemNotFoundErrorMessage;
        }
        else
        {
            rssNoItemsAvailableDescription += otherErrorMessage;
        }
        rssNoItemsAvailableDescription += String.Format(exceptionMessage, e.Message);
        xt.WriteElementString("description", rssNoItemsAvailableDescription);
    }

    xt.WriteEndElement(); // channel

    // Create an item for each returned changeset.
    foreach (Changeset change in changesetEnum)
    {
        xt.WriteStartElement("item");
        string onBehalfOf = null;

        // Include the committer if it differs from the changeset owner.
        // This occurs when a proxy agent performs the checkin.
        if (!change.Owner.Equals(change.Committer))
        {
            onBehalfOf = String.Format(rssItemOnBehalfOf, change.Owner);
        }

        // Generate a subject based on the checkin comment
        string subject = rssEmptyComment;
        if (!String.IsNullOrEmpty(change.Comment))
        {
            subject = change.Comment.Trim();
        }
        int max = Math.Min(subject.Length, maxSubjectLength);
        int newline = subject.IndexOf('\n', 0, max);
        if (newline != -1)
        {
            if (newline > 0 && subject[newline - 1] == '\r')
            {
                newline--;
            }
            subject = subject.Substring(0, newline);
        }
        if (subject.Length >= maxSubjectLength)
        {
            subject = String.Concat(subject.Substring(0, maxSubjectLength), "...");
        }

        xt.WriteElementString("title", String.Format(rssItemTitle, change.ChangesetId, subject));

        string csLink = HttpUtility.HtmlEncode(new ChangesetUri(
                                                     String.Format("{0}://{1}:{2}",
                                                                  Request.Url.Scheme,
                                                                  Request.Url.Host,
                                                                  Request.Url.Port),
                                                     change.ChangesetId,
                                                     UriType.Extended).ToUrl());

        xt.WriteElementString("description", String.Format(rssItemDescription,
                                                           change.ChangesetId,
                                                           change.Committer,
                                                           onBehalfOf,
                                                           change.CreationDate,
                                                           change.Changes.Length,
                                                           csLink,
                                                           change.Comment));
        xt.WriteElementString("link", csLink);

        xt.WriteElementString("subject", String.Format(rssItemSubject,change.Changes.Length));
        xt.WriteElementString("author", onBehalfOf == null ? change.Committer : change.Owner);
        xt.WriteElementString("pubDate", change.CreationDate.ToString());
        xt.WriteElementString("guid", change.ChangesetId.ToString());
        xt.WriteEndElement(); // item
    }
}
catch (Exception e)
{
    Response.StatusCode = 404;
    Response.End();
}
finally
{
    xt.Close();
    if (throttleRequests)
    {
        Context.Cache[Request.UserHostAddress] = Environment.TickCount;
    }
}
%>

Posted by jefflu | 2 Comments

Team Foundation: What's my server doing? (Part 2)

In my last post, I wrote about the mechanism within Team Foundation that records web method activity. You can mine the database to and learn much about how Team Foundation is being used. Recall that the web method information is written to a database so the full power of SQL queries is available.

List all Team Foundation activity

Combine the command and parameter table (the left join is necessary since some web method rows in the command table may not record parameter information -- more on this later)

select *
from tbl_command c
left join tbl_parameter p
on c.commandid = p.commandid

List all Team Foundation Version Control activity

Same as the previous query with the addition of filtering on the Application column (command table).

select *
from tbl_command c
left join tbl_parameter p
on c.commandid = p.commandid
-- Just Version Control (other choices: Data Warehouse, Integration, Work Item Tracking, Proxy)
where c.Application = 'Version Control'

Show the web methods with the longest execution time

The ExecutionTime column contains the execution time, in microseconds, of the web method.

select *
from tbl_command c
left join tbl_parameter p
on c.commandid = p.commandid
order by c.ExecutionTime desc

Show the web methods called the most

select command, count (command) as TimesCalled
from tbl_command c
group by command
order by TimesCalled desc

Show the web method calls made by userN

The IdentityName column contains the Windows account name of the caller.

select *
from tbl_command c
left join tbl_parameter p
on c.commandid = p.commandid
-- IdentityName contains the account name of the user (domain\account)
where c.IdentityName like '%userN%'

Show the web method calls made from Visual Studio

The UserAgent column contains the name of the executable from which the web method call originates.

select *
from tbl_command c
left join tbl_parameter p
on c.commandid = p.commandid
-- UserAgent contains the executable name
where c.Useragent like '%devenv.exe%'

Other tidbits

  • Rows in the parameter table contain the commandId value of the corresponding row in the command table. Note that there may be multiple rows in the parameter table for a single command
  • Parameters are recorded when (1) An error occurs during web method execution (indicated by the Status column of the command table set to -1); (2) The web method took longer than 30 seconds to execute (recall that the ExecutionTime values are in microseconds) or (3) the Web method logging level is set to All in the web.config file.
Posted by jefflu | 4 Comments

Team Foundation: What's my server doing?

We designed a mechanism to record web method activity for all of the Team Foundation web applications. Information about each web method invocation is recorded in a single database.

We don't provide any ways to get to that data -- however, if you have access to the data tier you can write a few SQL queries to see what your Team Foundation web applications are doing.

Web method logging is controlled by a setting in each web application's web.config file. The default configuration disables web method logging but it's pretty simple to enable it.

Locate each web.config file under the installation directory, i.e., D:\Program Files\Microsoft Visual Studio 2005 Enterprise Server.

In the <appSettings> section you should see something like the following:

     <!--  WEB METHOD LOGGING
          Specify whether web methods are to be logged always, never, or only
          when errors occurs. Without a value for commandLogging, web methods
          are not logged.  Valid values are:
              None     (web methods are never logged)
              Normal   (log everything, except certain high-frequency web methods)
              All      (log everything, even certain high-frequency web methods)
              OnError  (only log web methods that have errors)

              Values specified here take precedence over values set administratively via
              web method.
        -->
        <add key="commandLogging" value="None"/>

Change None to Normal or All and web method activity will now be recorded.

I'll go into the details of how we collect and record the data if enough people are interested. Note that the data is spooled for a short while before it's written to the database.

The database (for now, it's will be renamed) is called VSTEAMSCCAdmin. There are some historical reasons for including SCC in the name; it'll be called TFSActivityLogging in the shipping product.

There are two tables in this database: one records information about each web method and the other contains parameter information for each web method.

A simple query that shows web method activity plus the parameters (SOAP parameters) is (note: the LEFT join is necessary since there may not be values from the parameter table for each command):

select *
from tbl_command (nolock) c
left join tbl_parameter  (nolock) p
on c.commandid = p.commandid

The command table contains information about the web method itself and includes:

  • Command identifier A unique id used as a key in the parameter table
  • Application Identifies the Team Foundation Web Application servicing the web method
  • Command The name of the web method
  • StartTime When the web method was invoked
  • ExecutionTime How long the web method executed (microseconds)
  • IdentityName The user invoking the web method
  • IPAddress Origination of the web method execution request
  • UserAgent The provided user agent string (this will contain the name of the executable as well)
  • UniqueIdentifier (optional) When specified, this value correlates multiple web method records. E.g., a command like checkin will perform multiple web method calls. The identifier correlates the web method calls made for a specific occurrence of the command.
  • CommandIdentifier (optional) The name of the command-line tool that's invoking the web method (note that a single command like shelve will perform multiple web methods)

The parameter table is pretty straight forward -- note that parameters are conditionally reported.

  • CommandId Correlates to an entry in the command table
  • ParameterName The name of the parameter
  • ParameterValue The value of the parameter

 

Posted by jefflu | 9 Comments

Version Control RSS Feed

Recently, I wrote about a Team Foundation RSS feed. There may be a change required if you're seeing ASP.NET event log entries when your RSS reader contacts the feed complaining about the UriType (about line 194) not being recognized. Change UriType to ChangesetUriType and things should work.
Posted by jefflu | 2 Comments

Team Foundation Watson Reports: We're listening

We've been collecting Watson reports with the Beta 2 release. As of this week, we've created 81 bugs from the Watson reports that we've received from Beta2 and internal sites. All but 2 of the bugs have been resolved.

Please keep those Watson reports coming to us -- we actively review, prioritize, and follow up on them.

-jeff

Posted by jefflu | 0 Comments

Sample: Version Control RSS Feed

<%@ Page Language="c#" %>
<%@ OutputCache Duration="20" Location="Server" VaryByParam="state" VaryByCustom="minorversion" VaryByHeader="Accept-Language"%>

<%
// "Copyright © Microsoft Corporation.  All rights reserved.  These Samples are based in part on the Extensible Markup Language (XML) 1.0 (Third Edition) specification Copyright © 2004 W3C® (MIT, ERCIM, Keio. All rights reserved. http://www.w3.org/consortium/legal/2002/copyright-documents-20021231."
%>

<%
//This posting is provided "AS IS" with no warranties of any kind and confers no rights.  Use of samples included in this posting is subject to the terms specified at http://www.microsoft.com/info/cpyright.htm.
%>

<%@ Import Namespace="System" %>
<%@ Import Namespace="System.Collections" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Web" %>
<%@ Import Namespace="System.Xml" %>
<%@ Import Namespace="Microsoft.TeamFoundation.Client" %>
<%@ Import Namespace="Microsoft.TeamFoundation.VersionControl.Client"%>
<%@ Import Namespace="Microsoft.TeamFoundation.VersionControl.Common" %>

<%
// Generate an RSS feed for Team Foundation Source Code Control checkins
//
// Note: Only one request per "pester interval" from each system will be honored. See
//       pesterInterval below.
//
// This feed returns information about the most recent N checkins. See maxCheckinCount
// below.
//
// Invoking this page without any parameters returns information about all Team
// Foundation checkins up to the maximum count. If a filename is supplied, information
// about the most recent checkins for that file only are returned. Specify a filename
// by adding ?serverPath=<serverPath>. Note that the filename must be expressed as a server
// pathname without the leading $/.
//
// E.g., to see the activity for a file $/teamProjectA/myFile, add this to the Url
// ?serverPath=teamProjectA/myfile
//
// This page returns no items if the file does not exist.

// ***************************************************
// (Default: off) Set this to true to throttle user requests by host/username
// ***************************************************
bool throttleRequests = false;

// Ignore requests from the same machine within this interval
int pesterInterval = 30 * 1000; // expressed in milliseconds

if (throttleRequests)
{
    // If the originating system has been serviced recently, drop the request
    object lastRequest = Context.Cache[User.Identity.Name+Request.UserHostAddress];
    if (lastRequest != null)
    {
        if ((int) lastRequest + pesterInterval < Environment.TickCount)
        {
            return;
        }
    }
}

// Default -- everything under $/
string serverPathname = VersionControlPath.RootFolder;

try
{
    // Check whether information on a specific file was requested
    string pathname = Request.Params["serverPath"];
    if (pathname != null)
    {
        // The pathname does not contain a leading '$'
        serverPathname = VersionControlPath.Combine(VersionControlPath.RootFolder, pathname);
        // The pathname is valid.
    }
}
catch
{
    Response.StatusCode = 404;
    Response.End();
}

// The maximum number of changes to return per query
int maxCheckinCount = 50;

string rssVersion = "2.0";
string rssTtl = "5";
string rssLanguage = "en-US";
string rssLink = Request.Url.ToString();
string rssTitle = "Team Foundation RSS Feed for Source Code Control Checkins";
string rssGenerator = "Sample RSS Feed Generator for Team Foundation";
string rssDescription = "<p>This feed provides information on Team Foundation Checkins. Each Team Foundation checkin is manifested as a changeset. The changesets that have been created recently are listed here.</p><p>This feed contains the most recent <i>{0}</i> checkin(s).</p>";
string rssItemOnBehalfOf = "(on behalf of {0}) ";
string rssItemTitle = "Checkin {0} [{1} items]";
string rssNoItemsAvailableDescription = "An error occurred obtaining the latest checkin information from Team Foundation. Try again later.";
string rssItemDescription = "<p><a href=\"{5}\">Changeset {0}</a> was checked in by <i>{1} {2}</i>on {3}. This checkin includes changes to {4} item(s).</p><p>You can view the details of the checkin by selecting the provided link.</p>.";
string exceptionMessage = "<p>An exception occurred while getting updated information from Team Foundation; the detailed exception message is <i>{0}</i></p>";
string itemNotFoundErrorMessage = "<p>There were no items found.</p>";
string accessDeniedErrorMessage = "<p>You do not have permission to obtain the Team Foundation checkin information.</p>";
string otherErrorMessage = "<p>An error occurred. This may be a transient error or a permanent one. Please check the event log for messages from VSTF Source Code Control</p>";

Response.ContentType = "text/xml";
XmlTextWriter xt = new XmlTextWriter(Response.OutputStream, null);
// Begin creating the XML document
xt.WriteStartElement("rss");
xt.WriteAttributeString("version", rssVersion);
xt.WriteStartElement("channel");
xt.WriteElementString("title", rssTitle);
xt.WriteElementString("ttl", rssTtl);
xt.WriteElementString("link", rssLink);
xt.WriteElementString("pubDate", DateTime.Now.ToString());
xt.WriteElementString("language", rssLanguage);
xt.WriteElementString("generator", rssGenerator);
try
{
    RecursionType recursionType = RecursionType.Full;


    // Obtain the list of changes from the mid-tier; the number of changes is reported in
    // the channel description.
    IEnumerable changesetEnum = null;
    try
    {
        TeamFoundationServer Tfs = null;
        object cacheEntry = Context.Cache["TeamFoundationServer"];
        if (cacheEntry == null)
        {
            // Note: only works on localhost
            Tfs = new TeamFoundationServer("http://localhost:8080");
            Context.Cache["TeamFoundationServer"] = Tfs;
        }
        else
        {
            Tfs = (TeamFoundationServer) cacheEntry;
        }
        VersionControlServer Vcs = (VersionControlServer) Tfs.GetService(typeof(VersionControlServer));
        // Return changes
        changesetEnum = Vcs.QueryHistory(serverPathname,  // on this item
                                         VersionSpec.Latest, // item version
                                         0,               // that are not deleted
                                         recursionType,   // at or below this item
                                         null,            // user
                                         null,            // start version
                                         null,            // stop version
                                         maxCheckinCount, // Up to this many changes
                                         true,            // include changes
                                         false);

        int changeCount = 0;
        foreach (Changeset change in changesetEnum)
        {
            changeCount++;
        }
        xt.WriteElementString("description", String.Format(rssDescription, changeCount));
    }
    catch (Exception e)
    {
        if (e is ItemNotFoundException)
        {
            rssNoItemsAvailableDescription += itemNotFoundErrorMessage;
        }
        else
        {
            rssNoItemsAvailableDescription += otherErrorMessage;
        }
        rssNoItemsAvailableDescription += String.Format(exceptionMessage, e.Message);
        xt.WriteElementString("description", rssNoItemsAvailableDescription);
    }

    xt.WriteEndElement(); // channel

    // Create an item for each returned changeset.
    foreach (Changeset change in changesetEnum)
    {
        xt.WriteStartElement("item");
        string onBehalfOf = null;

        // Include the committer if it differs from the changeset owner.
        // This occurs when a proxy agent performs the checkin.
        if (!change.Owner.Equals(change.Committer))
        {
            onBehalfOf = String.Format(rssItemOnBehalfOf, change.Owner);
        }

        xt.WriteElementString("title", String.Format(rssItemTitle,
                                                     change.ChangesetId,
                                                     change.Changes.Length));

        string csLink = HttpUtility.HtmlEncode(new ChangesetUri(
                                                     String.Format("{0}://{1}:{2}",
                                                                  Request.Url.Scheme,
                                                                  Request.Url.Host,
                                                                  Request.Url.Port),
                                                     change.ChangesetId,
                                                     UriType.Extended).ToUrl());

        xt.WriteElementString("description", String.Format(rssItemDescription,
                                                           change.ChangesetId,
                                                           change.Committer,
                                                           onBehalfOf,
                                                           change.CreationDate,
                                                           change.Changes.Length,
                                                           csLink));
        xt.WriteElementString("link", csLink);
        xt.WriteElementString("author", onBehalfOf == null ? change.Owner : change.Committer);
        xt.WriteElementString("pubDate", change.CreationDate.ToString());
        xt.WriteElementString("guid", change.ChangesetId.ToString());
        xt.WriteEndElement(); // item
    }
}
catch (Exception e)
{
    Response.StatusCode = 404;
    Response.End();
}
finally
{
    xt.Close();
    if (throttleRequests)
    {
        Context.Cache[Request.UserHostAddress] = Environment.TickCount;
    }
}
%>

Posted by jefflu | 10 Comments

Part 3: What's my source control server doing? A RSS feed to keep track of checkins

I haven't forgotten about the RSS feed generator. There were some issues that I needed to resolve with the sample code before it can be released; this required a few changes -- these include:

  • Instead of using public methods in the Version Control server assemblies, the Version Control Client Object Model (OM) is used instead. The OM supports a rich API and is the intended, suggested, and supported way by which applications interact with Team Foundation.
  • You should note that the RSS feed generator is a sample and is provided "AS IS". The file contains the disclaimers and copyright notice.

Caveats

  • This posting is provided "AS IS" with no warranties of any kind and confers no rights.  Use of samples included in this posting is subject to the terms specified at http://www.microsoft.com/info/cpyright.htm.
  • It may not work with the RSS reader that you're using (I've been using SharpReader).
  • It may not work with your environment.
  • I have verified that it will work with our upcoming CTP. Changes made to Team Foundation subsequent may be incompatible with the RSS feed generator.
  • The RSS feed generator uses the credentials of the client invoking it to obtain information about checkins (read access to at least one item in the changeset is required to obtain information about the changeset).

RSS Feed Generator Features

  • User throttle: Within a 30-second interval, only the first request from each user is serviced (duplicate requests are ignored). You can disable this behavior by setting throttleRequests to false. The interval is determined by pesterInterval.
  • Path-specific subscriptions: You can include a server path (e.g., $/teamProject/somePathOfInterest) to subscribe to only the area(s) of interest. If server path specifies a folder, than any changes at or below that folder are included in the feed. If server path specifies a file, only changes to that file are in the feed. The default server path is the folder $/
  • Security: The caller's credentials are used to obtain information about checkin activity. The links returned in the feed are for changeset webviews.

Example

Each checkin is represented by an individual item in the feed; e.g., this is information about the 3rd checkin:

Changeset 3 was checked in by DOMAIN\user on 7/21/2005 12:56:05 PM. This checkin includes changes to 15 item(s).

You can view the details of the checkin by selecting the provided link.

Selecting the changeset link will display the webview of that changeset:

Check-in 3: fsdfsadf


Summary

Team Project(s):

507211250

Checked in by:

user

Checked in on:

7/21/2005 12:56:05 PM

Code Reviewer:

None

Performance Reviewer:

None

Security Reviewer:

None

Comment:

fsdfsadf


Resolved Work Items

Type

ID

Title

Status

Assigned To

Task

1

Setup: Set Permissions

Closed

jefflu

Task

2

Setup: Migration of Source Code

Closed

jefflu


Items

Name

Change

Folder

WindowsApplication52

add

$/jefflu507211250

WindowsApplication52.sln

add

$/jefflu507211250/WindowsApplication52

WindowsApplication52.vssscc

add

$/jefflu507211250/WindowsApplication52

WindowsApplication52

add

$/jefflu507211250/WindowsApplication52

Form1.cs

add

$/jefflu507211250/WindowsApplication52/WindowsApplication52

Form1.Designer.cs

add

$/jefflu507211250/WindowsApplication52/WindowsApplication52

Program.cs

add

$/jefflu507211250/WindowsApplication52/WindowsApplication52

Properties

add

$/jefflu507211250/WindowsApplication52/WindowsApplication52

AssemblyInfo.cs

add

$/jefflu507211250/WindowsApplication52/WindowsApplication52/Properties

Resources.Designer.cs

add

$/jefflu507211250/WindowsApplication52/WindowsApplication52/Properties

Resources.resx

add

$/jefflu507211250/WindowsApplication52/WindowsApplication52/Properties

Settings.Designer.cs

add

$/jefflu507211250/WindowsApplication52/WindowsApplication52/Properties

Settings.settings

add

$/jefflu507211250/WindowsApplication52/WindowsApplication52/Properties

WindowsApplication52.csproj.vspscc

add

$/jefflu507211250/WindowsApplication52/WindowsApplication52

WindowsApplication52.csproj

add

$/jefflu507211250/WindowsApplication52/WindowsApplication52


Notes:
- All dates and times are shown in Eastern Daylight Time (GMT -04:00:00).
Provided by Microsoft Visual Studio® 2005 Team System

Installation

The RSS feed generator is a .ASPX file so all you need to do is copy the file under the Version Control subdirectory and then subscribe to the feed. The following example assumes that Team Foundation is using port 8080 (default). 'SCC' is the name of the Version Control web application (you can view the web site layout using inetmgr).

To install and subscribe to the feedFor example,

   copy CheckinRssFeed.aspx %ProgramFiles%\Microsoft Visual Studio 2005 Enterprise Server\HATIISDIR

Using your RSS reader, subscribe to http://machine:8080/SCC/CheckinRssFeed.aspx

 

 

Posted by jefflu | 1 Comments

Why we want Watson reports

One of the features we added to the mid-tier web services was the ability to create and file reports when error and/or unexpected conditions occur. When these happen, a information about the condition is gathered and then a report is filed with the Watson reporting mechanism.

The next time an administrator logs in to the mid-tier machine, the Watson reporting UI should appear asking if you want to send the queued reports. Please consider doing so -- we really need this information to see what kind of real-world problems are occurring. We include information about the error condition and stack trace of the thread active when the condition occurred in the report.

You might wonder what happens to the Watson reports that are sent to Microsoft. Each week, the Team Foundation Watson Review team (a representative from the test organization and myself) review the Watson report that arrived in the last week. We focus on the newly arriving Watson reports and the reports that have been reported the most. We used to investigate only the top 10 reports (as measured by our internal Watson tool) but have now extended our focus to the top 60 or so (we look at any report that has been reported more than a few times).

We look at reports from Beta 2 deployments and some other key deployments or test beds that are internal to Microsoft.

After we investigate the set of Watson reports, we send a summarization that includes the Watson report, status, the bug (if any -- many problems have already been fixed) details, and the developer responsible for fixing it. This summarization gets wide visibility by design -- we even send it to our PUM's boss. I'm happy  to report that we've been making great progress in fixing issues illustrated by the Watson process.

One more thing -- and I can't stress this enough -- we really do look at the Watson reports that we receive and make every attempt to correct the issue(s) that are contained in the report. Please continue to allow the reports to be sent.

Posted by jefflu | 0 Comments

Part 2: What's my source control server doing? A RSS feed to keep track of checkins

In my last post, I started describing an RSS feed generator for Team Foundation Source Control. Note that this is an experimental endeavor and not supported in any way by Microsoft as it is not part of the Team Foundation product.

I described, in brief, the features that it supports and touched on security. I won't repeat that information here.

Feed Contents

The RSS feed provides information about recent checkins -- there is one RSS item for each checkin that has occurred. Each item contains the following

  • The date, time, and number of changes in the checkin.
  • Who performed the checkin
  • A link to a changeset webview.

The contents of the item include meta information about the checkin but not the checkin details (note the security information in my last post) and a link to the changeset webview. I'll talk about what those are in a minute.

By default, i.e., if a file path is not supplied, information about all checkins is returned. When a file path is part of the Url, i.e.,g url?filename=filepath, information about only the filepath is returned. If it refers to a directory, information about checkins at or below the directory is returned; otherwise, information about checkins for that file only are returned.

Changeset Webview

Webviews are a core Team Foundation feature that provide web-based access to Team Foundation artifacts. Each Team Foundation component has a set of artifacts. E.g., Workitem Tracking has an artifact for a work item (no surprise) and Source Control has artifacts for changesets and files.

The changeset webview shows the changeset, e.g., who created the changeset, when it was created, what changes are included, etc. In Beta2, the changeset webview appearance is crude but contains all of the information from a changeset. We've polished the appearance and included extra information in the bits after Beta2.

RSS Feed Generator

The RSS feed provides information about checkins; for each checkin, a changeset webview link is provided. I've mentioned what a changeset webview contains so its now time to discuss the RSS feed. Once installed, you can subscribe to the RSS feed by specifying the .aspx page that generates the feed, e.g.,

http://server:port/applicationName/application/CheckinRssFeed.aspx

The RSS feed generator uses the following .NET and Team Foundation assemblies:

  • System (System, System.Web, System.XML namespaces)
  • Microsoft.VisualStudio.Hatteras.Bis
  • Microsoft.VisualStudio.Hatteras.Util
  • Microsoft.VisualStudio.Hatteras.Server

Keep in mind that assembly names and namespaces will be changing for RTM. For Beta2, project names (e.g., Hatteras, Currituck) and temporary names (Bis) are still used. Assembly names, namespaces, classes, methods and other places where internal project names are in use will be different.

In my next post, I'll start