Migrating Redmine issues to VSTS work items with the REST API

Friday, 22 June 2018

Redmine is an open-source project management/issue tracking system. I wanted to copy issues out of Redmine and import them into a Visual Studio Team Services project.

Extracting issues can be done by using the "CSV" link at the bottom of the Issues list for a project in Redmine. This CSV file doesn't contain absolutely everything for each issue (eg. attachments and custom data from any plugins). Another alternative would be to query the database directly, but that wasn't necessary for my scenario.

To migrate the data to VSTS you can use a simple PowerShell script, making use of the VSTS REST API.

You'll need to create a Personal Access Token. Be aware that all items will be created under the account linked to this token - there's no way that I'm aware of that you can set the "CreatedBy" field to point to another user.

Notice in the script how we handle different fields for different work items types (eg. Product Backlog Items use the 'Description' field, whereas Bugs use 'Repro Steps'), and for optional fields (eg. not all Redmine issues had the 'Assignee' field set).

The full set of fields (and which work item types they apply to) is documented here. If you have more fields in Redmine that can be mapped to ones in VSTS then go ahead and add them.

Get programming in F#

Monday, 11 June 2018

I’m really interested in learning more about functional programming. It isn’t something I knew much about, but the benefits of reducing mutability (and shared state) promoted by functional languages and functional style are enticing.

To that end, I recently bought a copy of Isaac Abraham’s new book “Get programming in F#. A guide for .NET Developers”.

I have no background in functional languages at all, so I was looking for a “gentle” introduction to the F# language, without getting hung up on a lot of the functional terminology that seems to make learning this stuff a bit impenetrable for the newcomer. This book delivers.

The structure of the book is in 10 “units”, which in turn are broken down into separate “lessons” (each lesson is a separate chapter).

Here's my notes from each unit:

Unit 1 – F# and Visual Studio

Unit 2 – Hello F#

Unit 3 – Types and functions

Unit 4 – Collections in F#

Unit 5 – The pit of success with the F# type system

Unit 6 – Living on the .NET platform

Unit 7 – Working with data

Unit 8 – Web programming

Unit 9 – Unit testing

Unit 10 – Where next?

VSTS and TeamCity – Wrapping up

Sunday, 7 January 2018

Part 4 in a series on integrating VSTS with TeamCity

Wouldn't it be great if TeamCity and VSTS had full builtin support for each other? Well yes, yes it would! Maybe that will happen soon.

If I knew Java well, I could probably have a go at writing a TeamCity addin that encapsulates most of the what the pull request server does - but the idea of spending a few weeks getting up to speed with Java/TeamCity development doesn’t excite me that much.

TeamCity 2017.2 adds VSTS Git support to the Commit Status Publisher build feature. I haven’t been able to try this out yet (due to some other bugs in 2017.2 preventing me from upgrading), but it is possible this could remove or reduce the requirement for the build completion handler.

VCS post-commit hook

Now you've seen how to to use the APIs for TeamCity and VSTS, you might also want to implement another optimisation - adding a VCS post-commit hook. You add an additional service hook in VSTS that notifies TeamCity that there's a code change so that TeamCity knows it should grab the latest commit(s).
  1. In VSTS Project Settings, go to the Service Hooks tab
  2. Click '+' to add a new service hook
  3. Select Web Hooks
  4. In Trigger on this type of event, select Code pushed
  5. Optionally, review the Filters and just check the Repository (and branch) that should trigger the event.
  6. In the URL, enter something like https://www.example.com/app/rest/vcs-root-instances/commitHookNotification?locator=vcsRoot:(type:jetbrains.git,count:99999),property:(name:url,value:%2Fbuildname,matchType:contains),count:99999
    the locator can vary depending on your individual requirements
  7. Enter the username and password to authenticate with TeamCity
  8. Set Resource details to send, Messages to send and Detailed messages to send to None
  9. Click Test to confirm that everything works.

The nice thing about this is that rather than TeamCity blindly polling VSTS, VSTS is telling TeamCity when it has something of interest.

VSTS with TeamCity – Configuration

Wednesday, 3 January 2018

Part 3 in a series on integrating VSTS with TeamCity

Now that we've created the pull request server, we need to configure VSTS and TeamCity so that they can send event messages to it.


If you followed the steps in the sample tutorial, this will be familiar.
  1. Go to the Service Hooks tab for your project
  2. Click on the + icon
  3. Choose Web Hooks and click Next
  4. Select Pull request created.
    New Service Hooks Subscription dialog window screenshot
  5. If appropriate, select a specific repository and click Next
  6. In URL, enter the URL that VSTS will use to connect to the pull request server, including a query string that defines which TeamCity build should be queued.
    Eg. If the pull request server is hosted at https://www.example.com/pullrequestserver and the TeamCity build type id is My_CI_Build then you’d use https://www.example.com/pullrequestserver?buildTypeId=My_CI_Build
  7. In Username and Password, enter the credentials that will be used to authenticate with TeamCity
  8. Leave Resource details to send as All.
  9. Set Messages to send and Detailed messages to send to None
  10. Click on Test to try it out.
  11. Click on Finish to save this service hook.
  12. Repeat these steps to create another service hook for Pull request updated, also setting the Change filter to Source branch updated.
With the service hooks in place, you can now go to the Branches page, and click on the … (more actions) icon and choose Branch policies.
Selecting branch policy from more actions menu

The Add status policy button should be enabled, and clicking on that you should be able to find the pull request server listed in the drop down.


To allow TeamCity to call the pull request server, you will need to install the Web Hooks plugin for TeamCity. With that in place, go to the build configuration page in TeamCity, and you’ll see a new WebHooks tab.
  1. Click on add build Webhooks, then Click to create new WebHook for this build and add a new web hook for the project
  2. In the URL, enter the URL that TeamCity will use to connect to the pull request server.
    Eg. If the pull request server is hosted at https://www.example.com/pullrequestserver, you would use https://www.example.com/pullrequestserver/buildComplete.
  3. Set the payload format to Legacy webhook (JSON)
  4. Clear all the trigger events except On Completion Trigger when build successful and Trigger when build fails
  5. Click Save

In the VCS Root settings for the VSTS Git repository, set Branch Specification to

We don't need TeamCity to trigger the builds for the pull request branches as the pull request server will be queuing those builds, but we do still want TeamCity to trigger the master builds.

In the build configuration VCS Trigger, set the Branch filter to +:<default>

With all that configuration done, creating a new pull request in VSTS should now trigger a branch build in TeamCity. When the build completes, the status is posted back to VSTS, allowing the pull request to be completed by merging the changes into master.

VSTS with TeamCity – Building a pull request server

Tuesday, 2 January 2018

Part 2 in a series on integrating VSTS with TeamCity

This is a web service that handles web hook messages triggered by events in VSTS and TeamCity.

I started by using the sample Create a pull request status server with Node.js. As the title suggests, this is a Node application and it uses the Express library to run as a web server. I’d never written a Node app before, but it turns out it is surprisingly easy. If you’re starting from scratch, I’d recommend following the sample tutorial first, so you get up to speed with creating a really simple Node app that VSTS can talk to. Once you’ve got that working, then you should be well placed to use the code samples below.

There’s really two main processes we need to support:

  1. VSTS – Pull Request Created or Updated → Trigger TeamCity build of branch</li>
  2. TeamCity build complete → Tell VSTS if build was success or failure</li>

It’s also worth highlighting a couple of design goals for the pull request server:

NPM packages

 "dependencies": {
    "basic-auth": "^2.0.0",
    "body-parser": "^1.18.2",
    "express": "^4.16.2",
    "teamcity-rest-api": "0.0.8",
    "vso-node-api": "^6.2.8-preview"
  "devDependencies": {
    "@types/basic-auth": "^1.1.2",
    "@types/body-parser": "^1.16.8",
    "@types/express": "^4.0.39",
    "@types/node": "^8.5.1",
    "nodemon": "^1.13.3",
    "ts-node": "^4.0.2",
    "tslint": "^5.8.0",
    "typescript": "^2.6.2"

Note that the following code snippets have been ‘tidied’ for the blog post - the original has a fair bit of file and console logging which is invaluable when diagnosing what’s going on as you’re modifying the logic. I also switched to using TypeScript as I really appreciated the type checking it provides.

Get things ready

import * as auth from 'basic-auth';
import * as bodyParser from 'body-parser';
import * as express from 'express';
import * as fs from 'fs';
import * as vsts from 'vso-node-api';
import { GitPullRequestStatus, GitStatusState } from 'vso-node-api/interfaces/GitInterfaces';

const teamcity = require('teamcity-rest-api');

const app = express();
app.use(bodyParser.json({ limit: '5mb'}));

const collectionURL = process.env.COLLECTIONURL;
const token = process.env.VSTSTOKEN;

const authHandler = vsts.getPersonalAccessTokenHandler(token!);
const connection = new vsts.WebApi(collectionURL!, authHandler);

const vstsGit = connection.getGitApi();

Handling a pull request event

app.post('/prserver', (req, res) => {
    const user = auth(req);

    const buildTypeId = req.query.buildTypeId;

    if (!buildTypeId || !user) {

    const mergeStatus = req.body.resource.mergeStatus;

    if (mergeStatus !== 'succeeded') {

    // Get the details about the PR from the service hook payload
    const repoId = req.body.resource.repository.id;
    const pullRequestId = req.body.resource.pullRequestId;
    const commitId = req.body.resource.lastMergeSourceCommit.commitId;

    // This needs to match the branch pattern in TeamCity
    const branchName = pullRequestId;

    // We need to find out the pull request IterationId
    vstsGit.getPullRequestIterations(repoId, pullRequestId, undefined, false)
    .then((iterations: any[]) => {
        return iterations.filter(item => item.sourceRefCommit.commitId === commitId)[0].id;
    .then((iterationId: string) => {

        // trigger TeamCity build
        const client = teamcity.create({
            url: 'http://localhost:8111',
            username: user.name,
            password: user.pass

        // This is the data we are posting to TeamCity
        // tslint:disable-next-line:max-line-length
        const buildNodeObject = `<build branchName='${branchName}'><buildType id='${buildTypeId}' /><properties><property name='vsts.pullRequestId' value="${pullRequestId}" inherited="false"/><property name='vsts.repositoryId' value="${repoId}" inherited="false"/><property name='vsts.iterationId' value="${iterationId}" inherited="false"/></properties></build>`;

            .then((buildStatus: any) => {
                // Build the status object that we want to post.
                // Assume that the PR is ready for review...
                const prStatus = {
                    'iterationId': iterationId,
                    'state': 'pending',
                    'description': `Queued build: ${buildStatus.buildTypeId}`,
                    'targetUrl': buildStatus.webUrl,
                    'context': {
                        'name': 'teamcity',
                        'genre': 'continuous-integration'
                    _links: null

                // Post the status to the PR
                vstsGit.createPullRequestStatus(prStatus as any, repoId, pullRequestId);
            }, (reason: any) => {
                // error logging
    }).catch((reason: any) => {
        // error logging

    res.send('Received the POST');

Handling build completion from TeamCity

app.post('/prserver/buildComplete', (req, res) => {
    const pullRequestIdNode = req.body.build.teamcityProperties.find((item: any) => item.name === 'vsts.pullRequestId');

    if (!pullRequestIdNode) {
        // ignore if we don't have expected property

    const pullRequestId = pullRequestIdNode.value;

    const repositoryId = req.body.build.teamcityProperties.filter((item: any) => item.name === 'vsts.repositoryId')[0].value;
    const iterationId = req.body.build.teamcityProperties.filter((item: any) => item.name === 'vsts.iterationId')[0].value as number;

    const prStatus = {
        iterationId: iterationId,
        state: GitStatusState.Succeeded,
        'description': 'Build succeeded',
        'targetUrl': req.body.build.buildStatusUrl,
        'context': {
            'name': 'teamcity',
            'genre': 'continuous-integration'
        _links: null

    if (req.body.build.buildResult !== 'success') {
        prStatus.state = GitStatusState.Failed; // 'failed';
        prStatus.description = `Build result: ${req.body.build.buildResult}`;

    // Post the status to the PR
    vstsGit.createPullRequestStatus(prStatus as any, repositoryId, pullRequestId);


During development I ran the Node app from the command-line and made extensive use of console and file logging.

Once I was satisfied it was working as expected, I switched to using iisnode to run it under IIS.

Where this app runs depends on how accessible your TeamCity server is. If TeamCity is Internet-facing then the server could run anywhere (maybe even as something like an Azure Function). If TeamCity is running behind your firewall, then you might choose to host the pull server internally too. Note that VSTS does need to be able to contact the pull server – either directly (so the pull server itself is Internet-facing), or using a reverse proxy service like ngrok.

In the next post, I’ll describe how to configure TeamCity and VSTS to call the pull request server.