Migrating Redmine issues to devops/vsts work items with acmemapper

We love devops/vsts (will use devops starting now) and used it for long time for our continuous integration and continuous deployment. But on few projects we started long time ago with Redmine which I do really like: lighting fast, simple and cover without hassle 90% of our needs.

But, in order to align with company policy and improve cross team cooperation, we had to make a reasonable choice and switch to devops. ok though, but migration is mandatory, no way we loose all these valuable information.

Migration are often painful and adjusting how we map one information to another. Hopefully, while I was thinking on how not wasting too much time, I realized I could use the mapper engine we have developed for our enterprise service program : acmemapper acmemapper

used by a basic C# homebrew tool called redmine2vsts available on github

DEVOPS REST API for work item is pretty easy: the main idea is an json array of fields with following format

[
  {
    "op": "add",
    "path": "field 1",
    "from": null,
    "value": "value 1"
  },
  {
    "op": "add",
    "path": "field 2",
    "from": null,
    "value": "value 2"
  }
]

As input, for simplicity, I'm using redmine-api for getting redmine issue in a strong type Redmine.Net.Api.Types.Issue

Not an issue for acmemapper which is providing templated input/output types with help of json.net library in the backend, convert plenty of strong types into something supported by acmemapper, either as input or output or both.

public D Map<S, D>(string entityName, S source) where D : new()  

The main logic is mapping redmine issue into JObject and perform a linq query for formating it like devops expects it.

 Redmine.Net.Api.Types.Issue issue = redmine_issue;

 var mappedissue = mapper.Map<Issue, JObject>("issue", issue);
 var vstsissue = mappedissue.Values<JProperty>().Select(x => new VSTSField {
                    op = "add",
                    path = x.Name,
                    value = x.Value.ToObject<dynamic>() }).ToList();
    public class VSTSField
    {
        string from = null;
        public string op { get; set; }
        public string path { get; set; }
        public dynamic value { get; set; }
    }

VSTSField is used rather a full anonymous object because it's not possible to set from to null in anonymous type declaration.

How does it look like ?

Redmine.Net.Api.Types.Issue

mapped JObject

extrapolated devops object

Last but not least, creating the mapping file definition taking advantage of acmemapper modifiers and operators such map, ignoreIfNull and patternValue in order to match your redmine

  • trackers
  • categories
  • versions
  • ...

below the definition file used on ACME project

Enjoy, C# redmine2vsts program was intended to be one shot usage explaining the code quality.

Fabien Camous

Read more posts by this author.