0 Comments

So if you haven't heard yet VSO Extensions are now in a private preview where you can sign up to get into the preview on extensions integration site. These extensions in the shortest sentence a supported way of doing customizations to VSO that will replace any of the "hacky" extensions that you may be playing around with at the moment like Tiago Pascal's Task Board Enhancer or maybe you have even created your own following similar steps to what I show in my TFS 2013 Customization book.

This post aims to give you a super quick guide on how to get started, you will need to go through the integrations site to really get into detail. It has most of what you will find in most posts but gives you a little something extra that most posts wouldn't have like tips on free stuff Smile

File, New Project

The easiest way to get a basic something in VSO is to just create a new project.

Create/Configure Project

We are going to create a new Type Script project

 New_Project_2015-06-18_20-08-23

You should have something like below now

2015-06-18_20-09-35

Configure SSL in IIS Express

When you have the VSO Time Ticker project selected head over to the properties window

2015-06-18_20-17-56

Change SSL Enabled to True

2015-06-18_20-18-36

Take note of the SSL Url that is now available to you.

Add a extensions.json

Let's add a extensions.json manifest file that will be used to inform VSO what our projects actually about

Add_New_Item_-_VSO_Time_Ticker_2015-06-18_20-11-47

and drop in the content below, replace the baseUri property to include the port you have been assigned for SSL for the project.

{
"namespace": "VSO-Time-Ticker",
"version": "0.0.1",
"name": "Time Ticker",
"description": "A simple extension for Visual Studio Online of a Time Ticker",
"provider": {
"name": "Gordon Beeming"
},
"baseUri": "https://localhost:44300/",
"icon": "https://localhost:44300/images/some-icon.png",
"contributions": {
"vss.web#hubs": [
{
"id": "time",
"name": "Time",
"groupId": "home",
"order": 22,
"uri": "index.html",
"usesSdk": true,
"fullPage": false
}
]
}
}

Get the SDK

Navigate to GitHub to the samples project and grab the VSS.SDK.js file. Save a copy of that to a scripts folder inside a sdk folder and add it to your project.

2015-06-18_20-27-15

Include our App js files

While we here let's build the project, show hidden folders and add the app.js and app.js.map files to the project

2015-06-18_20-29-042015-06-18_20-29-58
If you are using source control you should also at this point undo those files being added source control and then also add them to be excluded otherwise you may get a weird error when it comes time to build your project on a build server (TypeScript : Emit Error: Write to file failed...).
2015-06-18_20-33-152015-06-18_20-33-49
The reason we want these as part of the solution is so that when we do web deploy later they are deployed as well Smile.

Add our app icon

Make a images folder and add a image called some-icon.png to it
2015-06-18_20-44-48

Move App js file

Move your App.ts, App.js and App.js.map into a scripts folder. If you have source you might need to re undo and ignore those extra files.

2015-06-18_20-51-48

Setup index.html

This is a rather simple step, replace the reference to app.js with one to sdk/Scripts/VSS.SDK.js so it will look something like

2015-06-18_20-49-40

Add the following script just inside your body tag

<script type="text/javascript">
// Initialize the VSS sdk
VSS.init({
setupModuleLoader: true,
moduleLoaderConfig: {
paths: {
"Scripts": "scripts"
}
}
});

// Wait for the SDK to be initialized
VSS.ready(function () {
require(["Scripts/app"], function (app) { });
});
</script>
So at this stage your full index.html page will look like
<!DOCTYPE html>

<html lang="en">
<head>
<meta charset="utf-8" />
<title>TypeScript HTML App</title>
<link rel="stylesheet" href="app.css" type="text/css" />
<script src="sdk/Scripts/VSS.SDK.js"></script>
</head>
<body>
<script type="text/javascript">
// Initialize the VSS sdk
VSS.init({
setupModuleLoader: true,
moduleLoaderConfig: {
paths: {
"Scripts": "scripts"
}
}
});

// Wait for the SDK to be initialized
VSS.ready(function () {
require(["Scripts/app"], function (app) { });
});
</script>
<h1>TypeScript HTML App</h1>

<div id="content"></div>
</body>
</html>

Update App.ts

In your App.ts file remove the window.onload function and replace it with it's body so your App.ts file will look like below

class Greeter {
element: HTMLElement;
span: HTMLElement;
timerToken: number;

constructor(element: HTMLElement) {
this.element = element;
this.element.innerHTML += "The time is: ";
this.span = document.createElement('span');
this.element.appendChild(this.span);
this.span.innerText = new Date().toUTCString();
}

start() {
this.timerToken = setInterval(() => this.span.innerHTML = new Date().toUTCString(), 500);
}

stop() {
clearTimeout(this.timerToken);
}

}

var el = document.getElementById('content');
var greeter = new Greeter(el);
greeter.start();

Run App

Running your app with ctrl + F5 you will get a blank app that does nothing Smile

TypeScript_HTML_App_-_Internet_Explorer_2015-06-18_20-57-12

Changed the url to point to the SSL version of your site just to make sure everything is working

2015-06-18_20-59-24

Our App is now complete Open-mouthed smile

Install your extension

If you have signed up for the private preview you should see a tab in the admin section of your account called Extensions like so

2015-06-18_21-01-57

Click Install, and then Browse 

2015-06-18_21-06-49
browse for your extension.json file
Open_2015-06-18_21-07-59

Click Open and then OK

2015-06-18_21-11-56

Your extension is now installed

2015-06-18_21-17-24

View it on VSO

Go to a team project home page and you should now see a Time hub, click on it

image

Once you land here you will the time Smile

image

That's 1 extension in the bag but having this run on your local machine is probable not want you would want because nobody else can see it.

Publishing you app

You could buy an SSL certification but that costs a lot and most people don't have that kind of money laying around for fun apps and extensions so we'll turn to Azure. We will now right click on our project and click publish

2015-06-18_21-45-47

If you setup an Azure site already you can import the publish settings but I haven't so I'm going to click on Microsoft Azure Web Apps

Publish_Web_2015-06-18_21-46-16

and then click on New (again if you have a site already you can select it in this list)

Select_Existing_Web_App_2015-06-18_21-47-26

Select a name and click Create

Create_Web_App_on_Microsoft_Azure_2015-06-18_21-48-31

it will now take a small bit to setup your azure resource

Create_Web_App_on_Microsoft_Azure_2015-06-18_21-49-28

and then auto magically configure everything you need Smile, click Publish

Publish_Web_2015-06-18_21-49-58

After the publish is finish your site will launch

TypeScript_HTML_App_-_Internet_Explorer_2015-06-18_21-51-30

Something that you will notice is that this is http but and not https as we said earlier we require. So let's see what happens if we add a s in there Smile

TypeScript_HTML_App_-_Internet_Explorer_2015-06-18_21-53-07

Everything still works Open-mouthed smile.

Last bit of manifest changes

Now that we have a publicly accessible website running on https (for FREE) we can take that url and replace what we currently have in our manifest so it will now look like this

{
"namespace": "VSO-Time-Ticker",
"version": "0.0.2",
"name": "Time Ticker",
"description": "A simple extension for Visual Studio Online of a Time Ticker",
"provider": {
"name": "Gordon Beeming"
},
"baseUri": "https://vso-hello-world.azurewebsites.net/",
"icon": "https://vso-hello-world.azurewebsites.net/images/some-icon.png",
"contributions": {
"vss.web#hubs": [
{
"id": "time",
"name": "Time",
"groupId": "home",
"order": 22,
"uri": "index.html",
"usesSdk": true,
"fullPage": false
}
]
}
}
Re-install your extension

2015-06-18_21-56-38

and refresh your extension in VSO

image

You will notice now that it obviously still works Smile, if you close Visual Studio and it still works you know it working Smile and I suppose you can check fiddler for where it's reading the files from.

Links

For more info on VSO Extensions visit http://aka.ms/vsoextensions.

A pretty neat getting started post is also on that site at https://www.visualstudio.com/en-us/integrate/extensions/get-started/visual-studio.

Microsoft has a project out on GitHub as well that is quite advanced in the API's that it uses and can be found at https://github.com/Microsoft/vso-team-calendar.

If you want a light overview over everything then you can get their VSO Extension Samples out on GitHub as well using the link https://github.com/Microsoft/vso-extension-samples.

Complete Sample code for this post is also out on Github at https://github.com/Gordon-Beeming/VSO-Time-Ticker

0 Comments

In these posts we will be going through creating different elements that can be found in the Team Explorer, the aim is to give you the ground knowledge required to extend Team Explorer your way. For an example of some great Team Explorer plugins that demonstrate how much you can do take a look at Team Rooms for Visual Studio 2013 and News which were created by Utkarsh Shigihalli and Tarun Arora for Team Explorer in Visual Studio 2013.

All sample code that is worked on through out any of the Team Explorer Samples will be hosted on GitHub at https://github.com/Gordon-Beeming/TeamExplorerSamplePlugin.

What you'll need

Create the a new blank Visual Studio Package

Open Up Visual Studio in Administrator mode and create a new Visual Studio Package

image

Use the following options in the Visual Studio Package Wizard

Page 1 of 7 - Select your language as Visual C# and Generate a new key file to sign the assembly

image

Page 2 of 7 - Enter the Basic VSPackage Information

image

Page 3 of 7 - leave all unchecked

image

Page 7 of 7 - You should select Integration Test Project and Unit Test Project but for now deselect these Smile

image

Click Finish, Visual Studio will now crate the base Project for you and automatically open the source.extension.vsixmanifest file. With this file open there is one thing that we are going to want to add. We are going to require the MEF Component Asset. Thanks must go to Utkarsh Shigihalli for helping with this part Smile. Switch to the Assets tab and click New, select the options to complete the window as below

image

The last step to setup this solution for our sample plugin is to add a reference to Microsoft.TeamFoundation.Controls assembly which can be found by default in the folder C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\ReferenceAssemblies\v4.5\and System.ComponentModel.Composition.

image

Your solution is now ready to start adding Team Explorer Extensions.

Take a look at Getting start with a Team Explorer Plugin for VS 2013 Part 2 where we will be adding a new Team Explorer Navigation Item.

1 Comments

In Getting start with a Team Explorer Plugin for VS 2013 Part 2 we created a Team Explorer Navigation Item and we will be using that item in this post and therefore assume that you have been through it already and created the navigation item.

Creating a Team Explorer Page

Add a new const to the GuidList class (in the Guids file) like below

public const string sampleTeamExplorerPage = "8C4F4A24-38C3-451C-A55F-9532EA61E841";

Create a class called SampleTeamExplorerPage and replace the contents with code below

namespace Company.TeamExplorerSamplePlugin
{
using System;
using System.ComponentModel;

using Microsoft.TeamFoundation.Controls;

[TeamExplorerPage(GuidList.sampleTeamExplorerPage)]
public class SampleTeamExplorerPage : ITeamExplorerPage
{
private IServiceProvider serviceProvider;

private bool isBusy;

public void Cancel()
{
}

public object GetExtensibilityService(Type serviceType)
{
return null;
}

public void Initialize(object sender, PageInitializeEventArgs e)
{
this.serviceProvider = e.ServiceProvider;
}

public bool IsBusy
{
get
{
return this.isBusy;
}
private set
{
this.isBusy = value;
this.FirePropertyChanged("IsBusy");
}
}

public void Loaded(object sender, PageLoadedEventArgs e)
{
}

public object PageContent
{
get
{
return null;
}
}

public void Refresh()
{
}

public void SaveContext(object sender, PageSaveContextEventArgs e)
{
}

public string Title
{
get
{
return "Sample Page";
}
}

public void Dispose()
{
}

public event PropertyChangedEventHandler PropertyChanged;

private void FirePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}

Note how we used the GuidList.sampleTeamExplorerPage guid string that we created in the TeamExplorerPage attribute. We also set some basic properties as in the previous post. Now that we have a page we will want to set the contents of the page

Creating Team Explorer Page Contents

Add a new user control to your project called SamplePageControl.xaml

image

We will not be adding anything fancy to this control, just add a TextBlock and set the Text property so that we are able to see that it is loaded, below is a sample of this

<UserControl x:Class="Company.TeamExplorerSamplePlugin.SamplePageControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="Sample Page Content"></TextBlock>
</Grid>
</UserControl>

You will at this stage need to add a reference to the assembly System.Xaml.

Defining the Team Explorer Page Content

To define the Team Explorer Page Content all we need to do is change the PageContent property to return an instance of our SampleTeamExplorerPage class

public object PageContent
{
get
{
return new SamplePageControl();
}
}

This will now load our new user control each time this page is loaded.

Navigating to a Team Explorer Page

All that is required to navigate to a Team Explorer Page is the guid of that page. To enable the navigation in our sample go to the SampleTeamExplorerNavigationItem class and change the Execute method to look like below

public void Execute()
{
var service = this.GetService<ITeamExplorer>();
if (service == null)
{
return;
}
service.NavigateToPage(new Guid(GuidList.sampleTeamExplorerPage), null);
}

You are also able to easily navigate to any other Page that is in Team Explorer if you know the guid, to make it easier to navigate to the default pages that are standards in TFS Microsoft has added a class called TeamExplorerPageIds which contains the Ids of all the default pages. For example if you wanted to navigate to the home page you would use the code below in your execute method or any other place

public void Execute()
{
var service = this.GetService<ITeamExplorer>();
if (service == null)
{
return;
}
service.NavigateToPage(new Guid(TeamExplorerPageIds.Home), null);
}

You'll notice now if you run the project that when you click on the sample button you are taken through to our new page and the page content is visible

 image

That's all that was required to create a Team Explorer Page with Content Smile.

In the Getting start with a Team Explorer Plugin for VS 2013 Part 4 we will be creating a new Team Explorer Section and then making some contents in the section content link through to this page.

1 Comments

In Getting start with a Team Explorer Plugin for VS 2013 Part 3 we created a Team Explorer Page and we will be using that item in this post and therefore assume that you have been through it already and created the page.

Creating a Team Explorer Section

Add a new const to the GuidList class (in the Guids file) like below

public const string sampleTeamExplorerSection = "09C3A4DF-7040-4AC4-BA8B-0740B53BD9D7";

Create a class called SampleTeamExplorerSection and replace the contents with code below

namespace Company.TeamExplorerSamplePlugin
{
using System;
using System.ComponentModel;

using Microsoft.TeamFoundation.Controls;

[TeamExplorerSection(GuidList.sampleTeamExplorerSection, TeamExplorerPageIds.Home, 100)]
public class SampleTeamExplorerSection : ITeamExplorerSection
{
private IServiceProvider serviceProvider;

private bool isBusy;

private bool isExpanded = true;

private bool isVisible = true;

public void Initialize(object sender, SectionInitializeEventArgs e)
{
this.serviceProvider = e.ServiceProvider;
}

public event PropertyChangedEventHandler PropertyChanged;

public void Cancel()
{
}

public object GetExtensibilityService(Type serviceType)
{
return null;
}

public bool IsBusy
{
get
{
return this.isBusy;
}
private set
{
this.isBusy = value;
this.FirePropertyChanged("IsBusy");
}
}

public bool IsExpanded
{
get
{
return this.isExpanded;
}
set
{
this.isExpanded = value;
this.FirePropertyChanged("IsExpanded");
}
}

public bool IsVisible
{
get
{
return this.isVisible;
}
set
{
this.isVisible = value;
this.FirePropertyChanged("IsVisible");
}
}

public void Loaded(object sender, SectionLoadedEventArgs e)
{
}

public void Refresh()
{
}

public void SaveContext(object sender, SectionSaveContextEventArgs e)
{
}

public object SectionContent
{
get
{
return null;
}
}

public string Title
{
get
{
return "Sample Section";
}
}

public void Dispose()
{
}

private void FirePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}

Note how we used the GuidList.sampleTeamExplorerSection guid string that we created in the TeamExplorerSection attribute. We also set some basic properties as in the previous posts. Note also that we used a Parent Page Id of TeamExplorerPageIds.Home in the TeamExplorerSection attribute which will mean that the section will show on the home Team Explorer Page. Another thing to notice is that the IsExpanded Property is defaulted to true, this will allow our section to be expanded by default when loaded.

Creating Team Explorer Section Contents

Add a new user control to your project called SampleSectionControl.xaml

image

We will be adding a TextBlock and setting the Text property so that we are able to see that it is loaded, we will also be adding a button that we will use to navigate to our Team Explorer Page from the C# code, below is a sample of this

XAML

<UserControl x:Class="Company.TeamExplorerSamplePlugin.SampleSectionControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<StackPanel>
<TextBlock Text="Sample Section Content"></TextBlock>
<Button Click="Button_Click" Content="Open Sample Page"></Button>
</StackPanel>
</Grid>
</UserControl>

C#

namespace Company.TeamExplorerSamplePlugin
{
using System;
using System.Windows;
using System.Windows.Controls;

using Microsoft.TeamFoundation.Controls;

/// <summary>
/// Interaction logic for SampleSectionControl.xaml
/// </summary>
public partial class SampleSectionControl : UserControl
{
private readonly IServiceProvider serviceProvider;

public SampleSectionControl(IServiceProvider serviceProvider)
{
this.InitializeComponent();

this.serviceProvider = serviceProvider;
}

private void Button_Click(object sender, RoutedEventArgs e)
{
var service = this.GetService<ITeamExplorer>();
if (service == null)
{
return;
}
service.NavigateToPage(new Guid(GuidList.sampleTeamExplorerPage), null);
}

public T GetService<T>()
{
if (this.serviceProvider != null)
{
return (T)this.serviceProvider.GetService(typeof(T));
}
return default(T);
}
}
}

Defining the Team Explorer Page Content

To define the Team Explorer Section Content all we need to do is change the PageContent property to return an instance of our SampleTeamExplorerSection class

public object SectionContent
{
get
{
return new SampleSectionControl(serviceProvider);
}
}

This will now load our new user control each time this section is loaded.

If you run the solution and go to the Team Explorer you will see that our section exists at the bottom of the Team Explorer Home  page.

image

If you click the Open Sample Page button you are taken through to the sample page created in the previous post.

That's all that is required for the Team Explorer Section with custom content embedded.

0 Comments

In Getting start with a Team Explorer Plugin for VS 2013 Part 1 we setup a base to start extending Team Explorer, this post assumes you have already have the base project setup.

Creating a new Team Explorer Navigation Item

Add a new const to the GuidList class (in the Guids file) like below

public const string sampleTeamExplorerNavigationItem = "8C35B3DF-D7CC-45BC-B958-BFAE3E157A21";

Add any image (to be used as the icon) to the Resources.resx file and call it SampleImage like below

image

Create a class called SampleTeamExplorerNavigationItem and replace the contents with code below

namespace Company.TeamExplorerSamplePlugin
{
using System;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Drawing;
using System.Windows.Forms;

using Microsoft.TeamFoundation.Controls;
using Microsoft.VisualStudio.Shell;

[TeamExplorerNavigationItem(GuidList.sampleTeamExplorerNavigationItem, 100)]
public class SampleTeamExplorerNavigationItem : ITeamExplorerNavigationItem
{
private Image image = Resources.SampleImage;

private bool isVisible = true;

private string text = "Sample Button";

[ImportingConstructor]
public SampleTeamExplorerNavigationItem([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}

private IServiceProvider serviceProvider { get; set; }

public Image Image
{
get
{
return this.image;
}
set
{
this.image = value;
this.FirePropertyChanged("Image");
}
}

public bool IsVisible
{
get
{
return this.isVisible;
}
set
{
this.isVisible = value;
this.FirePropertyChanged("IsVisible");
}
}

public string Text
{
get
{
return this.text;
}
set
{
this.text = value;
this.FirePropertyChanged("Text");
}
}

public void Execute()
{
MessageBox.Show("Execute Called");
}

public void Invalidate()
{
}

public void Dispose()
{
}

public event PropertyChangedEventHandler PropertyChanged;

private void FirePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

public T GetService<T>()
{
if (this.serviceProvider != null)
{
return (T)this.serviceProvider.GetService(typeof(T));
}
return default (T);
}
}
}

Note how we used the GuidList.sampleTeamExplorerNavigationItem guid string that we created in the TeamExplorerNavigationItem attribute, we also specified a priority of 100 saying that this navigation item should be high up on the list of navigation items. We specified in the Execute method that we want to see a message box to make sure our the event is being fired, we'll change this at a later stage, we also set some basic properties for the display of our button including the image we added earlier. If you run the project you will see in the Team Explorer that our Sample Button is visible.

image

Note when you click the button the message box displays as expected.

image

In the Getting start with a Team Explorer Plugin for VS 2013 Part 3 we will be creating a new Team Explorer Page and then changing our Execute method to navigate to this page.