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
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.