Skip to content

Commit

Permalink
vsts-sample-backlogs-panel extension
Browse files Browse the repository at this point in the history
  • Loading branch information
ajaypantangi committed Aug 10, 2016
1 parent cd35421 commit 61ce8d7
Show file tree
Hide file tree
Showing 20 changed files with 817 additions and 0 deletions.
4 changes: 4 additions & 0 deletions backlogs-panel/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
dist
scripts/**/*.js
*.vsix
32 changes: 32 additions & 0 deletions backlogs-panel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
## sample backlog panel ##

Sample backlog panel extension using Typescript and React.

### Structure ###

```
/scripts - Typescript code for extension
/img - Image assets for extension and description
/typings - Typescript typings
details.md - Description to be shown in marketplace
index.html - Main entry point
vss-extension.json - Extension manifest
```

### Usage ###

1. Clone the repository
1. `npm install` to install required dependencies
2. `grunt` to build and package the application

#### Grunt ####

Two basic `grunt` tasks are defined:

* `build` - Compiles TS files in `scripts` folder
* `package` - Builds the vsix package

#### Including framework modules ####

The VSTS framework is setup to initalize the requirejs AMD loader, so just use `import Foo = require("foo")` to include framework modules.
3 changes: 3 additions & 0 deletions backlogs-panel/details.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## sample backlog panel ##

Sample backlog panel extension using Typescript and React.
51 changes: 51 additions & 0 deletions backlogs-panel/gruntfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
module.exports = function (grunt) {
grunt.initConfig({
ts: {
build: {
tsconfig: true
},
options: {
fast: 'never'
}
},
exec: {
package: {
command: "tfx extension create --manifest-globs vss-extension.json",
stdout: true,
stderr: true
}
},
copy: {
scripts: {
files: [{
expand: true,
flatten: true,
src: ["node_modules/vss-web-extension-sdk/lib/VSS.SDK.min.js"],
dest: "dist",
filter: "isFile"
}]
},
styles: {
files: [{
flatten: true,
expand: true,
src: ["styles/main.css"],
dest: "dist",
filter: "isFile"
}]
}
},

clean: ["scripts/**/*.js", "*.vsix", "dist"]
});

grunt.loadNpmTasks("grunt-ts");
grunt.loadNpmTasks("grunt-exec");
grunt.loadNpmTasks("grunt-contrib-copy");
grunt.loadNpmTasks('grunt-contrib-clean');

grunt.registerTask("build", ["ts:build", "copy:scripts", "copy:styles"]);
grunt.registerTask("package", ["build", "exec:package"]);

grunt.registerTask("default", ["package"]);
};
Binary file added backlogs-panel/img/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions backlogs-panel/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!DOCTYPE html>

<html lang="en">
<head>
<meta charset="utf-8" />
<script src="dist/VSS.SDK.min.js"></script>

<link href="dist/main.css" rel="stylesheet"></link>
</head>
<body>
<script type="text/javascript">
// Initialize framework
VSS.init({
explicitNotifyLoaded: true,
usePlatformScripts: true,
moduleLoaderConfig: {
paths: {
"scripts": "dist"
}
}
});

// Load main entry point for extension
VSS.require(["dist/app"], function () {
// Loading succeeded
VSS.notifyLoadSucceeded();
});
</script>

<div class="hub-title">Details</div>
<div id="main"></div>
</body>
</html>
19 changes: 19 additions & 0 deletions backlogs-panel/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"devDependencies": {
"grunt": "~0.4.5",
"grunt-cli": "^0.1.13",
"grunt-contrib-clean": "^1.0.0",
"grunt-contrib-copy": "~0.8.2",
"grunt-exec": "~0.4.6",
"tfx-cli": "^0.3.13",
"tsd": "~0.6.5",
"typescript": "^1.8.10",
"vss-web-extension-sdk": "^1.95.2"
},
"name": "tags-mru",
"private": true,
"version": "0.0.0",
"dependencies": {
"grunt-ts": "^5.3.2"
}
}
20 changes: 20 additions & 0 deletions backlogs-panel/scripts/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/// <reference path='../typings/tsd.d.ts' />

import * as React from "react";
import * as ReactDOM from "react-dom";

import { MainComponent } from "scripts/main";
import { IWorkItemInfo } from "scripts/interfaces";

let element = document.getElementById("main");
let mainComponent: MainComponent;

ReactDOM.render(<MainComponent ref={(i) => mainComponent = i} />, element);

var panel = {
workItemSelectionChanged: (workItemInfos: IWorkItemInfo[]) => {
mainComponent.setWorkItemIds(workItemInfos.map(wi => wi.workItemId));
}
};

VSS.register("detailsPanelObject", panel);
37 changes: 37 additions & 0 deletions backlogs-panel/scripts/components/assignedto.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/// <reference path='../../typings/tsd.d.ts' />

import * as React from "react";
import { IDetailsWidgetProps } from "scripts/interfaces";
import { IIdentity } from "scripts/model";
import { BaseWidgetComponent } from "scripts/components/widgetbase";

// 'AssignedComponent' to render assignedTo field of a workitem
export class AssignedComponent extends BaseWidgetComponent<void> {
public render(): JSX.Element {
let identities = this._renderIdentities();

return <div className="widget">
<div className="title">People</div>
<div className="content assigned-to">
<ul className="">
{ identities }
</ul>
</div>
</div>;
}

private _renderIdentities() {
if (this.props.backlogDetailsModel) {
return this.props.backlogDetailsModel.identities.map(i => {
return <IdentityComponent key={ i.uniqueName } identity={ i } />;
});
}
}
}

// 'IdentityComponent' to render a identity
class IdentityComponent extends React.Component<{ identity: IIdentity }, void> {
public render(): JSX.Element {
return <li><img src={ this.props.identity.imageUrl } />&nbsp;{ this.props.identity.displayName }</li>;
}
}
52 changes: 52 additions & 0 deletions backlogs-panel/scripts/components/devdetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/// <reference path='../../typings/tsd.d.ts' />

import * as React from "react";
import { IDetailsWidgetProps } from "scripts/interfaces";
import { BaseWidgetComponent } from "scripts/components/widgetbase";
import { IPullrequest, IPullRequestState } from "scripts/model";

import VCContracts = require("TFS/VersionControl/Contracts");

// 'DevDetailsComponent' to render pullRequests associated with a workitem
export class DevDetailsComponent extends BaseWidgetComponent<void> {
public render(): JSX.Element {
if (!this.props.backlogDetailsModel || !this.props.backlogDetailsModel.pullRequests) {
return null;
}

let pullRequests = this.props.backlogDetailsModel.pullRequests;

return <div className="widget">
<div className="title">Development</div>
<div className="content">
{pullRequests.map((pullrequest, index) => {
return <div key={pullrequest.id} className="pull-request-item">
<div className="pull-request-icon">
<span className="icon bowtie-icon bowtie-tfvc-pull-request"></span>
</div>
<div className="pull-request-data">
<div className="pull-request-primary-data">
<span className="pull-request-assigned-to-icon"><img src={ pullrequest.user.imageUrl } /></span>
<span className="pull-request-primary-data">
<a href={ pullrequest.uri } target="_blank">
{ pullrequest.name }
</a>
</span>
</div>
<div className="pull-request-secondary-data">
<span>Created on { pullrequest.lastUpdated.toLocaleDateString() }</span>,&nbsp;<PullRequestState status={ pullrequest.status } />
</div>
</div>
</div>;
}) }
</div>
</div>;
}
}

// 'PullRequestState' renders pullRequest state
class PullRequestState extends React.Component<{ status: IPullRequestState }, void> {
public render(): JSX.Element {
return <span><span className={ this.props.status.iconClass }></span>&nbsp;{ this.props.status.state }</span>;
}
}
11 changes: 11 additions & 0 deletions backlogs-panel/scripts/components/spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/// <reference path='../../typings/tsd.d.ts' />

import * as React from "react";

export class SpinnerComponent extends React.Component<void, void> {
public render(): JSX.Element {
return <div className="indicator">
<div className="spinner bowtie-icon bowtie-spinner"></div>
</div>;
}
}
8 changes: 8 additions & 0 deletions backlogs-panel/scripts/components/widgetbase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/// <reference path='../../typings/tsd.d.ts' />

import * as React from "react";

import { IDetailsWidgetProps } from "scripts/interfaces";

export class BaseWidgetComponent<TState> extends React.Component<IDetailsWidgetProps, TState> {
}
36 changes: 36 additions & 0 deletions backlogs-panel/scripts/identityHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { IIdentity } from "scripts/model";
import * as Identities from "VSS/Identities/Picker/RestClient";
import * as VssService from "VSS/Service";
import * as Q from "q";

/**
* IdentityHelper
*/
export class IdentityHelper {
public static parseIdentityName(displayName:string, uniqueName: string): IIdentity {
var context = VSS.getWebContext();
return {
displayName: displayName,
uniqueName: uniqueName,
imageUrl: `${context.host.uri}/_api/_common/IdentityImage?id=&identifier=${encodeURIComponent(uniqueName)}&resolveAmbiguous=false&identifierType=0&size=0&__v=5`
};
}

public static parseUniquefiedIdentityName(uniquefiedName: string): IIdentity {
var context = VSS.getWebContext();
var nameParts: string[] = uniquefiedName.split(/<|>/);
return {
displayName: nameParts[0].trim(),
uniqueName: nameParts[1].trim(),
imageUrl: `${context.host.uri}/_api/_common/IdentityImage?id=&identifier=${encodeURIComponent(nameParts[1])}&resolveAmbiguous=false&identifierType=0&size=0&__v=5`
};
}

public static parseUniquefiedIdentityNames(uniquefiedNames: string[]): IIdentity[] {
var identities: IIdentity[] = [];
for(var name of uniquefiedNames){
identities.push(IdentityHelper.parseUniquefiedIdentityName(name));
}
return identities;
}
}
14 changes: 14 additions & 0 deletions backlogs-panel/scripts/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/// <reference path='../typings/tsd.d.ts' />

import * as React from "react";
import { WorkItem } from "TFS/WorkItemTracking/Contracts";
import { IModel } from "scripts/model";

export interface IWorkItemInfo {
workItemId: number;
workItemType: string;
}

export interface IDetailsWidgetProps extends React.Props<void> {
backlogDetailsModel: IModel;
}
Loading

0 comments on commit 61ce8d7

Please sign in to comment.