We have a plugin in tfs. This plugin notifies us when any work item changes. We can get work item details like id, changeset list as follows. But we need work item related branch name also.
Could you please help me, is there any way to get related branch name when workitem changed in below code.
Thanks
public EventNotificationStatus ProcessEvent(
IVssRequestContext requestContext,
NotificationType notificationType,
object notificationEventArgs,
out int statusCode,
out string statusMessage,
out ExceptionPropertyCollection properties)
{
statusCode = 0;
properties = null;
statusMessage = String.Empty;
try
{
if (notificationType == NotificationType.Notification && notificationEventArgs is WorkItemChangedEvent)
{
WorkItemChangedEvent ev = notificationEventArgs as WorkItemChangedEvent;
int workItemId = Convert.ToInt32(ev.CoreFields.IntegerFields[0].NewValue);
WriteLog("1WorkItemChangedEventHandler WorkItem " + ev.WorkItemTitle + " - Id " + workItemId.ToString() + " was modified");
}
}
catch (Exception)
{
}
return EventNotificationStatus.ActionPermitted;
}
You can use workItemStore.GetWorkItem(workItemId ) to get all work item info. Check the similar question here: How can I get a reference to the TFS WorkItem in a WorkItemChangedEvent?
If you use git, you can try the following example to get branch reference:
int workitemId = YOUR_ID;
WorkItemStore wiStore = new WorkItemStore("{YOUR_URI}");
WorkItem wi = wiStore.GetWorkItem(workitemId);
foreach (Link link in wi.Links)
{
if (link.BaseType == BaseLinkType.ExternalLink)
{
var brList = ((ExternalLink)link).LinkedArtifactUri.Split(new string[] { "%2F" }, StringSplitOptions.RemoveEmptyEntries).ToList();
brList.RemoveAt(0); //remove project and repo giuds
brList.RemoveAt(0);
brList[0] = brList[0].Replace("GB", ""); // remove GB prefix
Console.WriteLine("Branch: {0}", string.Join("/", brList));
}
}
Related
I have access to TFS users. I can get them into list but need to remove some of them from all the groups in our TFS. I've done so many researchers so far. Simply, I need to remove user from TFS groups.
I am open to any suggestions. Even for the crazy ones!
I've tried programmatical stuff. Don't have any clue.
After so many tries, finally got somewhere. My final code:
bool isError = false;
TeamFoundationIdentity memberId = ims.ReadIdentity(IdentitySearchFactor.DisplayName, id.DisplayName, MembershipQuery.Expanded, ReadIdentityOptions.None);
IIdentityManagementService2 ims2 = tcs.GetService<IIdentityManagementService2>();
string group = "Confidential Group;
TeamFoundationIdentity groupId = ims2.ReadIdentity(group);
if (groupId == null)
{
isError = true;
}
if (memberId == null)
{
isError = true;
}
if (!isError)
{
ims2.RemoveMemberFromApplicationGroup(groupId.Descriptor, memberId.Descriptor);
}
The error:
'TF50621: The Team Foundation group that you wish to manage is not owned by service host TEAM FOUNDATION, it is owned by . Please target your request at the correct host.'**
Check out the Azure Boards Team Tools.
It does most of what you need already:
message = string.Empty;
bool ret = true;
TeamFoundationTeam t = this.teamService.ReadTeam(this.projectInfo.Uri, team, null);
TeamFoundationIdentity i = this.identityManagementService.ReadIdentity(IdentitySearchFactor.AccountName, user, MembershipQuery.Direct, ReadIdentityOptions.None);
if (t == null)
{
message = "Team [" + team + "] not found";
ret = false;
}
if (i == null)
{
message = "User [" + user + "] not found";
ret = false;
}
if (ret)
{
this.identityManagementService.RemoveMemberFromApplicationGroup( t.Identity.Descriptor, i.Descriptor);
message = "User removed ";
}
return ret;
This function removes a user from a Team. A Team is a special kind of group, the function is easy to adapt to retrieve a Group to delete the user from though:
Replace
TeamFoundationTeam t = this.teamService.ReadTeam(this.projectInfo.Uri, team, null);
With:
string group = ...
var t = this.identityManagementService.ReadIdentity(group);
Or use the REST API :
DELETE https://vsaex.dev.azure.com/{organization}/_apis/GroupEntitlements/{groupId}/members/{memberId}?api-version=7.1-preview.1
I'm wanting to 'Create copy of work item' which is available via the UI, ideally via the API.
I know how to create a new work item, but the feature in the UI to connect all current parent links / related links, and all other details is quite useful.
Creating via this API is here: https://learn.microsoft.com/en-us/rest/api/azure/devops/wit/work%20items/create?view=azure-devops-rest-5.1
Any help would be greatly appreciated.
We cannot just copy a work item because it contains system fields that we should skip. Additionally your process may have some rules that may block some fields on the creation step. Here is the small example to clone a work item through REST API with https://www.nuget.org/packages/Microsoft.TeamFoundationServer.Client:
class Program
{
static string[] systemFields = { "System.IterationId", "System.ExternalLinkCount", "System.HyperLinkCount", "System.AttachedFileCount", "System.NodeName",
"System.RevisedDate", "System.ChangedDate", "System.Id", "System.AreaId", "System.AuthorizedAs", "System.State", "System.AuthorizedDate", "System.Watermark",
"System.Rev", "System.ChangedBy", "System.Reason", "System.WorkItemType", "System.CreatedDate", "System.CreatedBy", "System.History", "System.RelatedLinkCount",
"System.BoardColumn", "System.BoardColumnDone", "System.BoardLane", "System.CommentCount", "System.TeamProject"}; //system fields to skip
static string[] customFields = { "Microsoft.VSTS.Common.ActivatedDate", "Microsoft.VSTS.Common.ActivatedBy", "Microsoft.VSTS.Common.ResolvedDate",
"Microsoft.VSTS.Common.ResolvedBy", "Microsoft.VSTS.Common.ResolvedReason", "Microsoft.VSTS.Common.ClosedDate", "Microsoft.VSTS.Common.ClosedBy",
"Microsoft.VSTS.Common.StateChangeDate"}; //unneeded fields to skip
const string ChildRefStr = "System.LinkTypes.Hierarchy-Forward"; //should be only one parent
static void Main(string[] args)
{
string pat = "<pat>"; //https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate
string orgUrl = "https://dev.azure.com/<org>";
string newProjectName = "";
int wiIdToClone = 0;
VssConnection connection = new VssConnection(new Uri(orgUrl), new VssBasicCredential(string.Empty, pat));
var witClient = connection.GetClient<WorkItemTrackingHttpClient>();
CloneWorkItem(witClient, wiIdToClone, newProjectName, true);
}
private static void CloneWorkItem(WorkItemTrackingHttpClient witClient, int wiIdToClone, string NewTeamProject = "", bool CopyLink = false)
{
WorkItem wiToClone = (CopyLink) ? witClient.GetWorkItemAsync(wiIdToClone, expand: WorkItemExpand.Relations).Result
: witClient.GetWorkItemAsync(wiIdToClone).Result;
string teamProjectName = (NewTeamProject != "") ? NewTeamProject : wiToClone.Fields["System.TeamProject"].ToString();
string wiType = wiToClone.Fields["System.WorkItemType"].ToString();
JsonPatchDocument patchDocument = new JsonPatchDocument();
foreach (var key in wiToClone.Fields.Keys) //copy fields
if (!systemFields.Contains(key) && !customFields.Contains(key))
if (NewTeamProject == "" ||
(NewTeamProject != "" && key != "System.AreaPath" && key != "System.IterationPath")) //do not copy area and iteration into another project
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/" + key,
Value = wiToClone.Fields[key]
});
if (CopyLink) //copy links
foreach (var link in wiToClone.Relations)
{
if (link.Rel != ChildRefStr)
{
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/relations/-",
Value = new
{
rel = link.Rel,
url = link.Url
}
});
}
}
WorkItem clonedWi = witClient.CreateWorkItemAsync(patchDocument, teamProjectName, wiType).Result;
Console.WriteLine("New work item: " + clonedWi.Id);
}
}
Link to full project: https://github.com/ashamrai/AzureDevOpsExtensions/tree/master/CustomNetTasks/CloneWorkItem
I need to produce a list of Work Items from a production build, which is triggered by a merge. I use the below to get a list of changes, after pulling the Changeset Id from the build I care about
Uri tfsUri = new Uri("http://leecrp-tfs1:8080/tfs");
TfsTeamProjectCollection tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(tfsUri);
VersionControlServer vcs = tfs.GetService<VersionControlServer>();
List<String> changedThings = new List<String>();
Build bld = _builds.Where(x => x.BuildNumber == buildNumber).First();
if(bld != null)
{
int.TryParse(bld.SourceVersion, out int csId);
if (csId > 0)
{
Changeset changeset = vcs.GetChangeset(csId);
foreach(Microsoft.TeamFoundation.VersionControl.Client.Change change in changeset.Changes)
{
changedThings.Add(change.Item.ServerItem);
}
}
}
if(changedThings != null && changedThings.Count > 0)
{
lbChanges.DataSource = changedThings;
}
However, there does not appear to be anything in the object about the source of each check-in. As stated, I primarily need the Work Items so I can link back to our JIRA tickets later.
I have tried the following, but it returns nothing
IEnumerable<ExtendedMerge> mergeDetails = vcs.QueryMergesExtended(new ItemSpec(#"$/Project/Branch", RecursionType.Full), VersionSpec.Latest, VersionSpec.ParseSingleSpec(csId.ToString(), null), VersionSpec.ParseSingleSpec(csId.ToString(), null));
As it turns out, I just had to slightly modify an earlier attempt. The below gets me the merge details
VersionSpec versionSpec = VersionSpec.ParseSingleSpec(csId.ToString(), null);
ChangesetMergeDetails results = vcs.QueryMergesWithDetails(#"$/Project/SourceBranch", VersionSpec.Latest, 0, #"$/Project/TargetBranch", VersionSpec.Latest, 0, versionSpec, versionSpec, RecursionType.Full);
From there I use an excessive amount of foreach loops to get what I want:
foreach(Changeset thing in results.Changesets)
{
foreach(WorkItem workItem in thing.WorkItems.Distinct())
{
changedItems.Add(workItem.Id.ToString());
foreach (Revision revision in workItem.Revisions)
{
foreach (Field field in workItem.Fields)
{
if(field.Name == "History")
{
string thingICareAbout = revision.Fields[field.Name].Value.ToString();
if (thingICareAbout.Contains("TFS4JIRA"))
{
wiHistory.Add(ParseJIRT(thingICareAbout));
}
}
}
}
}
}
I'm trying to create a plugin in TFS 2017 ,that checks for Associated workitems before check-in and rejects the check-in if it does not meet few conditions. This was working for One Collection and One TeamProject. Now I'm trying to implement this this across multiple collections and team projects and it throws the following error "TF30063: You are not authorized to access "https://tfs.com//TeamProject" I'm pasting the code below, Please let me know what the possible issue would be .
{
// Logic Goes here ...
bool isNullComment = false;
bool isDevBranchChange = false;
bool isCodeReviewRequest = false;
bool isCodeReviewResponse = false;
bool isTaskAssociated = false;
//Read all submitted items
var changes = notification.GetSubmittedItems(requestContext);
//Check if the changes have any DEV branch changes
isDevBranchChange = changes.Any(change => change.ToUpper().Contains("/DEV/"));
if (isDevBranchChange)
{
isNullComment = string.IsNullOrEmpty(notification.Comment.ToString());
var assoWorkItems = notification.NotificationInfo.WorkItemInfo.Select(x => x.Id); //Read all associated workitem id's
TfsTeamProjectCollection connection = new TfsTeamProjectCollection(GetTfsUri(requestContext));
WorkItemStore wiStore = connection.GetService<WorkItemStore>();
//check if any of associated workitem has code review
foreach (int id in assoWorkItems)
{
WorkItem wi = wiStore.GetWorkItem(id);
// Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem wi = witClient.GetWorkItemAsync(id).Result;
if (wi.Type.Name.ToUpper().Equals("CODE REVIEW REQUEST"))
{
isCodeReviewRequest = true;
}
if (wi.Type.Name.ToUpper().Equals("TASK"))
{
isTaskAssociated = true;
}
if (wi.Type.Name.ToUpper().Equals("CODE REVIEW RESPONSE"))
{
isCodeReviewResponse = true;
}
}
if (!isCodeReviewRequest || isNullComment || !isCodeReviewResponse || !isTaskAssociated)
{
statusMessage = "Check-in Rejected. Check-in Comment, Task, Code Review Request and Code Review Response workitems are Required. \nFor more information, please contact TFS Administrators: vsalm#abc.com";
return EventNotificationStatus.ActionDenied;
}
}
}
catch (Exception exception)
{
statusMessage = "Caught an Exception" + exception.Message;
return EventNotificationStatus.ActionDenied;
// Any exception, TFS will disable it. So, Log it and eat it.
TeamFoundationApplicationCore.LogException("DecisionPoint plugin encountered the following error while processing events", exception);
}
}
return EventNotificationStatus.ActionPermitted;
}
private Uri GetTfsUri(IVssRequestContext _requestContext)
{
var locationService = _requestContext.GetService<ILocationService>();
return new Uri(locationService.GetServerAccessMapping(_requestContext).AccessPoint + "/" + _requestContext.ServiceHost.Name);
}
}
}
We are using TFS 2015. I'm having a problem in retrieving non XAML build information from my C# console App using the TFS API. Actually, for one project we have setup for XAML Build definations and for another project, it is non-XAML one. I am able to connect the TFS projects and able to get the XAML defination lists. However, while trying to fetch the Non-XAML definations or builds, it is always an empty array.My code is below,
static void Main(string[] args)
{
try
{
var tfsUri = (args.Length < 1) ? new Uri(ConfigurationManager.AppSettings["ServerUri"]) : new Uri(args[0]);
var userCreds = new NetworkCredential(
ConfigurationManager.AppSettings["Tfs.User"], ConfigurationManager.AppSettings["Tfs.Password"], ConfigurationManager.AppSettings["Tfs.Domain"]);
// var tfsServer = TfsConfigurationServerFactory.GetConfigurationServer(tfsUri, userCreds);
var tfsServer = new TfsConfigurationServer(tfsUri, userCreds);
tfsServer.EnsureAuthenticated();
//// Get the catalog of team project collections
var collectionNodes = tfsServer.CatalogNode.QueryChildren(new[] { CatalogResourceTypes.ProjectCollection }, false, CatalogQueryOptions.None);
//// List the team project collections
foreach (var collectionNode in collectionNodes)
{
//// Use the InstanceId property to get the team project collection
var collectionId = new Guid(collectionNode.Resource.Properties["InstanceId"]);
var teamProjectCollection = tfsServer.GetTeamProjectCollection(collectionId);
//// Get a catalog of team projects for the collection
var projectNodes = collectionNode.QueryChildren(new[] { CatalogResourceTypes.TeamProject }, false, CatalogQueryOptions.None);
foreach (var projectNode in projectNodes)
{
/*
Console.WriteLine("Collection: " + teamProjectCollection.Name); // Print the name of the team project collection
Console.WriteLine(" Team Project: " + projectNode.Resource.DisplayName); // List the team projects in the collection
Console.WriteLine(" Team Project Id: " + projectNode.Resource.Identifier);
*/
//// Get a catalog of team builds for the collection
var buildDefinitions = new BuildDefinition();
// var buildDetailList = buildDefinitions.GetBuildDefinitionListFromProject(teamProjectCollection, projectNode.Resource.DisplayName);
buildDefinitions.GetBuildDetailsFromProject(teamProjectCollection, projectNode.Resource.DisplayName, projectNode.Resource.Identifier);
}
}
}
catch (Exception ex)
{
ErrorLogger.LogError(ex);
}
}
public class BuildDefinition
{
public void GetBuildDetailsFromProject(TfsTeamProjectCollection tfsProjectCollection, string projectName, Guid projectId)
{
var buildService = tfsProjectCollection.GetService<IBuildServer>(); // (IBuildServer)tfs.GetService(typeof(IBuildServer));
var buildDefinitionsList = GetAllBuildDefinitionsFromTheTeamProject(buildService, projectName);
foreach (var buildDefinition in buildDefinitionsList)
{
var bdentities = new BuildDefinitionEntities
{
ProjectId = projectId,
ProjectName = buildDefinition.TeamProject,
BuildTypeId = Convert.ToInt32(buildDefinition.Id),
BuildTypeName = buildDefinition.Name,
ProjectDetailPath = string.Format("{0} > {1}", buildDefinition.TeamProject, buildDefinition.Name)
};
var buildDetailSpec = buildService.CreateBuildDetailSpec(buildDefinition);
buildDetailSpec.InformationTypes = null; // for speed improvement
buildDetailSpec.MinFinishTime = DateTime.Now.AddDays(-21); // to get only builds of last 3 weeks
buildDetailSpec.MaxBuildsPerDefinition = 1; // get only one build per build definintion
buildDetailSpec.QueryDeletedOption = QueryDeletedOption.ExcludeDeleted; // get only active builds
buildDetailSpec.QueryOrder = BuildQueryOrder.FinishTimeDescending; // get the latest build only
buildDetailSpec.QueryOptions = QueryOptions.All;
var buildDetailList = buildService.QueryBuilds(buildDetailSpec).Builds;
//// List the team builds for the collection
foreach (var buildDetail in buildDetailList)
{
bdentities.BuildId = buildDetail.RequestIds[0];
bdentities.BuildName = buildDetail.BuildNumber;
bdentities.ArtefactsPath = buildDetail.DropLocation ?? "No Artefacts";
bdentities.BuildCompleted = Convert.ToDateTime(buildDetail.FinishTime) > Convert.ToDateTime(buildDetail.StartTime)
? Convert.ToDateTime(buildDetail.FinishTime)
: Convert.ToDateTime(buildDetail.StartTime);
bdentities.BuildStatus = buildDetail.Status.ToString();
bdentities.SourceGetVersion = buildDetail.SourceGetVersion ?? string.Empty;
if (!string.IsNullOrEmpty(buildDetail.Quality))
{
bdentities.BuildQuality = buildDetail.Quality;
bdentities.BuildQualityChangedDate = Convert.ToDateTime(buildDetail.LastChangedOn);
}
BuildDefinitionDbOperations.ManageTfsBuildDefinitions(bdentities);
}
}
}
private static IBuildDefinition[] GetAllBuildDefinitionsFromTheTeamProject(IBuildServer buildServer, string projectName)
{
var buildDefinitionSpec = buildServer.CreateBuildDefinitionSpec(projectName);
buildDefinitionSpec.TriggerType = DefinitionTriggerType.All;
buildDefinitionSpec.Options = QueryOptions.Definitions;
return buildServer.QueryBuildDefinitions(buildDefinitionSpec).Definitions;
}
}
Actually, I am new to this Tfs system. Would you please guide me where I am wrong?
For the vNext/non-XAML build system you have to use the TFS REST API; you can find the details here