Azure DevOps Rest Api. 10. Manage Team Settings

<<Contents

Team settings are edited through the WorkHttpClient client, which includes team settings, boards, delivery plans, team performance, etc. In the context of this post, we will consider only the most frequently settings: an iteration path, an area path and team backlogs.

Before starting to work with team settings, you have to create a team context. To do this, you create a new TeamContext object with two parameters: a team project name and a team name. If we need to get a context of the default team, you can pass only a team project name. Create the default team context:

TeamContext teamContext = new TeamContext(TeamProjectName);

Iterations

By managing iterations, you can minimize a work of team administrators. If you are using shared iteration in all or many teams, you can add them automatically to all teams after you add iterations to your team project. You can also periodically clean old iterations, so they do not accumulate in team settings.

Before adding an iteration to a team, you have to add it to project settings. Creating of an iteration was described here: Manage Areas and Iterations in Team Projects.

The PostTeamIterationAsync method with the TeamSettingsIteration object and the context team as parameters will add iterations to a team. For a TeamSettingsIteration object is important only the unique ID of a iteration, which can be accessed through the GetClassificationNodeAsync method of the WorkItemTrackingHttpClient class. The example of adding an iteration:

WorkItemClassificationNode projectIt = WitClient.GetClassificationNodeAsync(TeamProjectName, TreeStructureGroup.Iterations, iterationName).Result; // get iteration from project

TeamSettingsIteration teamIt = WorkClient.PostTeamIterationAsync(new TeamSettingsIteration { Id = projectIt.Identifier }, teamContext).Result; //add iteration to a team by guid

Console.WriteLine(“Added iteration “ + teamIt.Name);

The DeleteTeamIterationAsync method removes an iteration from team settings with a team context and an ID of a iteration:

WorkItemClassificationNode projectIt = WitClient.GetClassificationNodeAsync(TeamProjectName, TreeStructureGroup.Iterations, iterationNameToRemove).Result;

WorkClient.DeleteTeamIterationAsync(teamContext, projectIt.Identifier).SyncResult(); //delete iteration from team settings by guid

Console.WriteLine(“Removed iteration “ + projectIt.Name);

With the GetTeamIterationsAsync method, you can view a set of team iterations. This method takes as parameters: a team context and an iteration time frame (past, present and future). The example of the method:

TeamSettingsIteration currentiteration = (WorkClient.GetTeamIterationsAsync(teamContext, “Current”).Result).FirstOrDefault();

The result is a TeamSettingsIteration object that contains:

  • Id – an iteration GUID
  • Name – an iteration name
  • Path – an iteration path
  • Attributes – an additional iteration attributes:
    • StartDate – a start date
    • FinishDate – a finish date
    • TimeFrame – an iteration timeframe: Past, Current, Future

The example:

List<TeamSettingsIteration> teamIterations = WorkClient.GetTeamIterationsAsync(teamContext).Result; //get all iterations

Console.WriteLine(“Team Iterations: “);

foreach (TeamSettingsIteration teamIteration in teamIterations)

Console.WriteLine(“{0} : {1} : {2}-{3}”, teamIteration.Attributes.TimeFrame, teamIteration.Name, teamIteration.Attributes.StartDate, teamIteration.Attributes.FinishDate);

Areas

Area makes sense to update automatically if you manage basic team responsibilities in an external system (CMDB, etc.) and they must be migrated into the relevant Azure DevOps Teams.

Before add area to a team, you have to add it to project settings (Manage Areas and Iterations in Team Projects).

You can view team areas through the method GetTeamFieldValuesAsync that will return a TeamFieldValues object with the following attributes:

  • DefaultValue – a default area value.
  • Values – an area list with TeamFieldValue objects that a team will control. The TeamFieldValue includes an area name (Value) and an attribute of including of subareas (IncludeChildren).

The example:

TeamFieldValues teamAreas = WorkClient.GetTeamFieldValuesAsync(teamContext).Result; //Get All Areas

Console.WriteLine(“Default Area: “ + teamAreas.DefaultValue);

Console.WriteLine(“Team Areas : “);

foreach (TeamFieldValue teamField in teamAreas.Values)

Console.WriteLine(“\t” + teamField.Value + ((teamField.IncludeChildren)? ” (include sub-areas)” : “”));

To update a set of areas, we have to use the UpdateTeamFieldValuesAsync method with the TeamFieldValuesPatch parameter. The TeamFieldValuesPatch class contains the same attributes as TeamFieldValues. Therefore, to update settings, you can get current values and add or remove the necessary ones:

string[] areas = { @”Application\WinClient”, @”Application\WebClient” };

TeamFieldValues currentTeamAreas = WorkClient.GetTeamFieldValuesAsync(teamContext).Result; // get current areas

TeamFieldValuesPatch teamAreasPatch = new TeamFieldValuesPatch();

List<TeamFieldValue> newTeamAreas = new List<TeamFieldValue>(currentTeamAreas.Values); // just copy old areas. Here we may remove unneeded areas

foreach (string area in areas)

newTeamAreas.Add(new TeamFieldValue { Value = TeamProjectName + “\\” + area, IncludeChildren = false }); // add new areas

teamAreasPatch.DefaultValue = currentTeamAreas.DefaultValue;

teamAreasPatch.Values = newTeamAreas;

TeamFieldValues updatedTeamAreas = WorkClient.UpdateTeamFieldValuesAsync(teamAreasPatch, teamContext).Result;

Common settings

Change common settings rarely may need through automation, perhaps if only basic team backlogs differ from the default settings and it is easier to change them automatically.

You can get current settings through the GetTeamSettingsAsync method, which returns TeamSetting with the attributes:

The example:

TeamContext teamContext = new TeamContext(TeamProjectName, TeamName);

TeamSetting teamSetting = WorkClient.GetTeamSettingsAsync(teamContext).Result;

Console.WriteLine(“Settings for the team “ + TeamName);

Console.WriteLine(“Backlog Iteration : “ + teamSetting.BacklogIteration.Name);

Console.WriteLine(“Default Iteration : “ + teamSetting.DefaultIteration.Name);

Console.WriteLine(“Macro of Iteration : “ + teamSetting.DefaultIterationMacro);

Console.WriteLine(“Categories of backlog:”);

foreach(string bkey in teamSetting.BacklogVisibilities.Keys)


if (teamSetting.BacklogVisibilities[bkey]) Console.WriteLine(“\t” + bkey);

Console.WriteLine(“Working days :”);

foreach (var wday in teamSetting.WorkingDays) Console.WriteLine(“\t” + wday.ToString());

switch (teamSetting.BugsBehavior)

{

case BugsBehavior.AsRequirements:

Console.WriteLine(“Bugs Behavior: Bugs in a requirements backlog.”);

break;

case BugsBehavior.AsTasks:

Console.WriteLine(“Bugs Behavior: Bugs in a sprint backlog as tasks.”);

break;

case BugsBehavior.Off:

Console.WriteLine(“Bugs Behavior: Find bugs through queries.”);

break;

}

The UpdateTeamSettingsAsync method updates team settings with the TeamSettingsPatch parameter, which has the same attributes as TeamSetting. This method will update only initialized attributes in a TeamSettingsPatch object:

TeamSetting teamSetting = WorkClient.GetTeamSettingsAsync(teamContext).Result;

TeamSettingsPatch teamSettingsPatch = new TeamSettingsPatch();

teamSettingsPatch.BacklogVisibilities = teamSetting.BacklogVisibilities;

if (teamSettingsPatch.BacklogVisibilities.ContainsKey(“Microsoft.EpicCategory”) && teamSettingsPatch.BacklogVisibilities[“Microsoft.EpicCategory”])

teamSettingsPatch.BacklogVisibilities[“Microsoft.EpicCategory”] = false;

if (teamSettingsPatch.BacklogVisibilities.ContainsKey(“Microsoft.FeatureCategory”) && teamSettingsPatch.BacklogVisibilities[“Microsoft.FeatureCategory”])

teamSettingsPatch.BacklogVisibilities[“Microsoft.FeatureCategory”] = false;

teamSetting = WorkClient.UpdateTeamSettingsAsync(teamSettingsPatch, teamContext).Result;

The sample application:

https://github.com/ashamrai/TFRestApi/tree/master/10.TFRestApiAppManageTeamSettings

Russian version

Azure DevOps Rest Api. 9. Manage Project Teams

<<Contents

The managing teams is a simple process through TeamHttpClient. It is enough to know a name of a team and a name of a team project in which that team is or will be. The team class is the WebApiTeam one with the following basic properties:

  • Id – the team id.
  • Name – the team name.
  • Description – the team description.
  • ProjectName – the team project.
  • ProjectId – the id of a team project.

The GetTeamAsync method returns the team information and uses only two parameters: the project name and team name or their identifiers. You can get the list of all teams through GetTeamsAsync with the team project name only:

List<WebApiTeam> teams = TeamClient.GetTeamsAsync(TeamProjectName).Result;

Console.WriteLine(“Project Teams:”);

foreach (WebApiTeam team in teams) Console.WriteLine(team.Name);

If you need to get information about team members, you can run the GetTeamMembersWithExtendedPropertiesAsync method with the same parameters. A team member is described by the TeamMember class, which contains two properties:

  • IsTeamAdmin – the team member is an administrator or not.
  • Identity – the user account information.

Exploring team members:

List<TeamMember> teamMembers = TeamClient.GetTeamMembersWithExtendedPropertiesAsync(TeamProjectName, TeamName).Result;

string teamAdminName = (from tm in teamMembers where tm.IsTeamAdmin == true
select tm.Identity.DisplayName).FirstOrDefault();

if (teamAdminName != null) Console.WriteLine(“Team Administrator:” + teamAdminName);

Console.WriteLine(“Team members:”);

foreach (TeamMember teamMember in teamMembers)

if (!teamMember.IsTeamAdmin) Console.WriteLine(teamMember.Identity.DisplayName);

To create and update a team, use the CreateTeamAsync and UpdateTeam methods, respectively. The main parameters are:

  • team – the WebApiTeam object which contains a team name and a description.
  • projectId – the team project name
  • teamId – the identifier of an existing team is used only when updating a name or a description.

Updating a team:

WebApiTeam team = TeamClient.GetTeamAsync(TeamProjectName, TeamName).Result;

WebApiTeam updatedTeam = new WebApiTeam {

Name = team.Name + ” updated”,

Description = team.Description.Replace(“Created”, “Updated”)

};

updatedTeam = TeamClient.UpdateTeamAsync(updatedTeam, team.ProjectName, team.Name).Result;

Console.WriteLine(“The team ‘{0}’ has been updated in the team project ‘{1}'”, updatedTeam.Name, updatedTeam.ProjectName);

The DeleteTeamAsync method removes an existing team:

TeamClient.DeleteTeamAsync(TeamProjectName, TeamName).SyncResult();

The sample application:

https://github.com/ashamrai/TFRestApi/tree/master/09.TFRestApiAppManageTeams

Russian version

Azure DevOps Rest Api. 8. Manage Areas and Iterations in Team Projects

<<Contents

The WorkItemTrackingHttpClient client manages areas and iterations with the same methods. Common class to describe an area or an iteration is WorkItemClassificationNode. It contains the following properties:

  • Id – the id of an area or an iteration.
  • Name – the name of an area or an iteration.
  • StructureType – the object type: «TreeNodeStructureType.Area» or «TreeNodeStructureType.Iteration».
  • Attributes – contains a start date and a finish date of iteration. They are accessible via the keys startDate и finishDate.

Creating areas and iterations can be performed through the CreateOrUpdateClassificationNodeAsync method, which includes the following parameters:

  • postedNode – the WorkItemClassificationNode object that contains a name and attributes.
  • project – the team project name to create an area or an iteration.
  • structureGroup – the object type: TreeStructureGroup.Areas or TreeStructureGroup.Iterations
  • path – the root path if necessary.

The example of creating a new area. To create a new area, you need to define only the name and the root path. If the root path is empty, the new area will be created on the top of a team project.

WorkItemClassificationNode newArea = new WorkItemClassificationNode();

newArea.Name = AreaName;

WorkItemClassificationNode result = WitClient.CreateOrUpdateClassificationNodeAsync(newArea, TeamProjectName, TreeStructureGroup.Areas, ParentAreaPath).Result;

The example of creating a new iteration. The iteration creation is the same with the area creation. Additionally, you may add the optional startDate and finishDate attributes.

WorkItemClassificationNode newIteration = new WorkItemClassificationNode();

newIteration.Name = IterationName;

newIteration.Attributes = new Dictionary<string, object>();

newIteration.Attributes.Add(“startDate”, StartDate);

newIteration.Attributes.Add(“finishDate”, FinishDate);

WorkItemClassificationNode result = WitClient.CreateOrUpdateClassificationNodeAsync(newIteration, TeamProjectName, TreeStructureGroup.Iterations, ParentIterationPath).Result;

You may update an area or an iteration with the UpdateClassificationNode method with the same attributes as for the CreateOrUpdateClassificationNodeAsync method. Additionally you need to pay attention to the path parameter. It will be mandatory for an area or iteration, if it has a parent. If for such areas or iterations do not specify a path, then they will be moved to the root of your team project. I.e. if we want to rename a node or change dates, we have to specify a path (just copy the current one).

To remove an area or iteration, you have to use the DeleteClassificationNodeAsync method with the parameters:

  • project – the project name that contains an iteration or an area.
  • structureGroup – the object type: TreeStructureGroup.Areas or TreeStructureGroup.Iterations
  • path – the path of an area or an iteration to remove.
  • reclassifyId – the id of an iteration or an area that will contain work items of a removed node.

The example of the remove operation.

WorkItemClassificationNode newNode = WitClient.GetClassificationNodeAsync(

TeamProjectName,

treeStructureGroup,

NewNodePath, 4).Result;

WitClient.DeleteClassificationNodeAsync(TeamProjectName, treeStructureGroup, NodePath, newNode.Id).SyncResult();

The sample application:

https://github.com/ashamrai/TFRestApi/tree/master/08.TFRestApiAppAreasAndIterations

Russian version

Azure DevOps Rest Api. 7. Add and Edit Work Item Links

<<Contents

Adding and updating links between work items performed by similar way with editing work item fields (methods UpdateWorkItemAsync and CreateWorkItemAsync.) Differs only information about the JsonPatchOperation object. To add a link:

patchDocument.Add(new JsonPatchOperation()

{

Operation = Operation.Add,

Path = “/relations/-“,

Value = new {

rel = RelName,

url = RelUrl,

attributes = new

{

comment = Comment,

isLocked = IsLocked // you must be an administrator to lock a link

}

}

});

Attributes to use:

  • Operation – an operation with reference mainly Add and Remove.
  • Path – may have the following values:
    • “/relations/-” – adds a new link between work items
    • “/relations/[link index]” – to edit properties or remove links, where [index links] is the ordinal number of a link in the WorkItem.Relations list.
  • Value – detailed link information that contains:
    • rel – the link type name: Link type reference.
    • url – the direct work item url.
    • attributes – the optional link attributes:
      • comment – the link comment.
      • isLocked – the link lock. If a link is locked, it can only be removed through the API.

Example of the add a link:

JsonPatchDocument patchDocument = new JsonPatchDocument();

patchDocument.Add(new JsonPatchOperation()

{

Operation = Operation.Add,

Path = “/relations/-“,

Value = new {

rel = “System.LinkTypes.Related”,

url = RelUrl,

attributes = new

{

comment = “Comment for the link”

}

}

});

return WitClient.UpdateWorkItemAsync(patchDocument, WiId).Result;

Update a link:

JsonPatchDocument patchDocument = new JsonPatchDocument();

patchDocument.Add(new JsonPatchOperation()

{

Operation = Operation.Add,

Path = “/relations/0”,

Value = new {

rel = “System.LinkTypes.Related”,

url = RelUrl,

attributes = new

{

comment = “New comment for the link”

}

}

});

return WitClient.UpdateWorkItemAsync(patchDocument, WiId).Result;

Remove a link:

JsonPatchDocument patchDocument = new JsonPatchDocument();

patchDocument.Add(new JsonPatchOperation()

{

Operation = Operation.Remove,

Path = “/relations/0”

});

return WitClient.UpdateWorkItemAsync(patchDocument, WiId).Result;

If you want to remove several links by one operation, the indexes should be added to JsonPatchDocument in reverse order. Because the removal mechanism performs successively. I.e. if you have a work item with two links, and you add to remove first the index 1 and then 2, then you will get the following error:

The sample application:

https://github.com/ashamrai/TFRestApi/tree/master/07.TFRestApiAppWorkItemLinks

Russian version

Azure DevOps Rest Api. 6. Upload and Download Work Item Attachments

<<Contents

When we automate some processes, it`s useful to use work item attachments which may include an application log, a screenshot, etc. The WorkItemTrackingHttpClient client uses the CreateAttachmentAsync and GetAttachmentContentAsync methods to upload and download attachments.

Example of the attachment uploading includes the following steps:

1. Create a stream to read a file content.

2. Create an attachment on the service through the CreateAttachmentAsync method that includes parameters: the stream and attachment name. You may use CreateAttachmentAsync without a file stream and with the name of the file. This way should be used if your file attachment on a local folder. Otherwise, the file name of an attachment will contain a file path rather than just its name.

3. Update a work item. Create JsonPatchDocument that adds a new link (the AttachedFile type) to a work item . We also pass the URL of attachments that step 2 will return.

AttachmentReference att;

using (FileStream attStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read))

att = WitClient.CreateAttachmentAsync(attStream, filePathSplit[filePathSplit.Length – 1]).Result; // upload the file

JsonPatchDocument patchDocument = new JsonPatchDocument();

patchDocument.Add(new JsonPatchOperation()

{

Operation = Operation.Add,

Path = “/relations/-“,

Value = new

{

rel = “AttachedFile”,

url = att.Url,

attributes = new { comment = “Comments for the file “ + filePathSplit[filePathSplit.Length – 1] }

}

});

WitClient.UpdateWorkItemAsync(patchDocument, WIId).Result;

To download attachments, you have to perform the following steps:

1. Get a work item and its links. The method GetWorkItemAsync uses the expand parameter with the WorkItemExpand.Relations value to get links.

2. Link with attachments contains the AttachedFile type in the Rel property of the WorkItemRelation class. If we need a specific file, we may find its name through the name attribute.

3. To download an attachment we need to get its ID. You can extract the ID from the attachment URL.

4. Then retrieve the stream through the GetAttachmentContentAsync method and save it to a file.

WorkItem workItem = WitClient.GetWorkItemAsync(WIId, expand: WorkItemExpand.Relations).Result;

foreach(var rf in workItem.Relations)

{

if (rf.Rel == “AttachedFile”)

{

string[] urlSplit = rf.Url.ToString().Split(new
char[] { ‘/’ }, StringSplitOptions.RemoveEmptyEntries);

using (Stream attStream = WitClient.GetAttachmentContentAsync(new Guid(urlSplit[urlSplit.Length – 1])).Result) // get an attachment stream

using (FileStream destFile = new FileStream(DestFolder + “\\” + rf.Attributes[“name”], FileMode.Create, FileAccess.Write)) // create new file

attStream.CopyTo(destFile); //copy content to the file

}

}

The sample application:

https://github.com/ashamrai/TFRestApi/tree/master/06.TFRestApiAppWorkItemAttachments

Russian version