cptcherry
cptcherry

Reputation: 53

'Create copy of work item' via REST API for Azure DevOps?

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.

Upvotes: 4

Views: 2525

Answers (1)

Shamrai Aleksander
Shamrai Aleksander

Reputation: 15998

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

Upvotes: 4

Related Questions