Menu

VSTS Tags MRU extension – Part 2

In the last post we ended up with a list of tags a user added last to a work item. The next step is now to keep an MRU list with tags from earlier sessions and update it when new tags are added.

Every time the user adds tags to a work item we want to merge these new tags with the (persisted) list of tags. We will keep a maximum of N tags and need to either only add tags to the list (if |tags| < N), or and and remove (if |tags| > N), or just reorder the tags in the list, so that the most recently used tags appear first in the final dropdown menu.

Keeping MRU list of tags

We want to store at maximum 5 tags for now, so we add a constant to our app.ts:

For later displaying tags in the context menu, we can just return the current queue which contains our tags in the correct order

public getTags(): string[] {
return this.queue;
}

Persisting Data from an extension

The VSTS framework provides the ExtensionDataServicewhich allows us to store key/value pairs or collections of JSON documents on the VSTS servers. Usage is quite simple, to store a value we just need to get an instance of the service, and call setValue with a key and a value. The value can be as simple as a string, or an JS object that’s transparently serialized to JSON. We can also pass a scope, that limits values either to an “account” or a “user” scope:

Persisting Tags

To keep it simple, loading tags is something we’d like to do only once at the beginning of a session, and then save every time tags are added. This way, we might run into conflicts if the user is working with different browser tabs/windows at the same time, but for this sample that last-write-wins concurrency is enough.

Using the ExtensionDataService we can modify the getInstance call to retrieve the list of tags from the data service using getValue and add every one using the addTag method we implemented above. Since service calls use Promises, we change getInstance to return a promise instead of a value. If the instance has already been created, we use Q(<value>) to return an immediately resolved promise, otherwise we retrieve tags and then create the instance:

Persisting tags will be done in another method, again getting a service instance (we could cache the instance), and calling then calling setValue. A promise is returned to allow callers to wait for the end of the call:

Updating work items

Final step is to add an action to the child menu items to actually add the tag to all selected work items. First we need to determine the selected work items. Unfortunately, the different VSTS views are not consistent in exposing the ids of selected work items right now. We need to look for different properties in the passed context depending on the view we are in. The logic is:

Backlog – array of numbers called workItemIds

Boards (Kanban/Iteration) – single number called id

Query results – array of numbers called ids

To unify in an array called ids we need to add the following code to the beginning of the getMenuItems method:

// Not all areas use the same format for passing work item ids.
// "ids" for Queries, "workItemIds" for backlogs, "id" for boards
var ids = context.ids || context.workItemIds;
if (!ids || context.id) {
// Boards only support a single work item
ids = [context.id];
}

Then, in the action handler of our child menu items, we need to:

Get the work items

For each work item

Get the existing value for the System.Tags field

Concatenate with the tag to add using “;” as separator

Update work item

Since we are changing work items not opened in any form right now, we need to use the REST API for the update operations. Some additional imports are required:

(Potential optimization would be to use the batch API for the work item updates instead of making a single call per work item)

All done

That’s it. The extension is done, can be published, and should mostly work as designed. I say mostly, because, if you remember, I mentioned earlier that there is one drawback, for which no workaround exists yet: When we add a tag, we need to use the REST API to update the work items. When we do this, the current VSTS view does not know that a work item has been updated, and does not refresh automatically.

Ideally, there would be a way to tell VSTS from an extension that a work item has been changed, but no such service is exposed at the moment. This means, users have to manually refresh the view or a specific work item after using the extension to add a new tag. For example like in this short gif (click to view full screen):

I do think, however, that the extension still provides value, and will publish it in the marketplace soon. Let me me know in the comments if anything is unclear or doesn’t work.