зеркало из
1
0
Форкнуть 0

Updated code to make build and deploy simpler

This commit is contained in:
Justin Marks 2018-12-04 16:25:17 -08:00
Родитель 4dc20cc0c7
Коммит 6fefc16252
22 изменённых файлов: 713 добавлений и 11879 удалений

1
.gitignore поставляемый
Просмотреть файл

@ -1,5 +1,4 @@
node_modules
typings
dist
*.vsix
*.log

Просмотреть файл

@ -1,4 +0,0 @@
{
"public": false,
"baseUri": "https://localhost:8080"
}

Просмотреть файл

@ -1,21 +0,0 @@
{
"public": true,
"files": [
{
"path": "src",
"addressable": true
},
{
"path": "libs",
"addressable": true
},
{
"path": "marketplace",
"addressable": true
},
{
"path": "img",
"addressable": true
}
]
}

Просмотреть файл

@ -16,8 +16,8 @@
.settings-control {
margin-top: 20px;
margin-left: 20px;
}
.combo {
width: 400px;
}
.settings-control .combo {
width: 400px;
}

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 421 B

После

Ширина:  |  Высота:  |  Размер: 421 B

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 4.3 KiB

После

Ширина:  |  Высота:  |  Размер: 4.3 KiB

Просмотреть файл

@ -1,28 +0,0 @@
var webpackConfig = require('./webpack.config');
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['mocha', 'chai', 'sinon'],
files: [
'src/**/*.tests.ts'
],
exclude: [
],
preprocessors: {
'src/**/*.tests.ts': ['webpack']
},
webpack: {
module: webpackConfig.module,
resolve: webpackConfig.resolve
},
reporters: ['mocha'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['PhantomJS'],
singleRun: false,
concurrency: Infinity
})
}

12098
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,46 +1,15 @@
{
"description": "",
"main": "webpack.config.js",
"scripts": {
"clean": "rimraf dist *.vsix",
"dev": "webpack-dev-server --hot --progress --colors --content-base ./src --https",
"package:dev": "node ./scripts/packageDev",
"publish:dev": "npm run package:dev && node ./scripts/publishDev",
"build:release": "npm run clean && mkdir dist && webpack --progress --colors --output-path ./dist -p",
"publish:release": "npm run build:release && node ./scripts/publishRelease",
"dev:test": "karma start",
"test": "karma start --single-run",
"postinstall": "typings install"
},
"author": "Christopher Schleiden",
"license": "MIT",
"version": "1.0.0",
"devDependencies": {
"chai": "^4.1.2",
"copy-webpack-plugin": "^4.5.0",
"css-loader": "^0.28.10",
"cwd": "^0.10.0",
"karma": "^1.7.1",
"karma-chai": "^0.1.0",
"karma-mocha": "^1.3.0",
"karma-mocha-reporter": "^2.2.5",
"karma-phantomjs-launcher": "^1.0.4",
"karma-sinon": "^1.0.5",
"karma-webpack": "^2.0.13",
"mocha": "^4.1.0",
"node-sass": "^4.7.2",
"phantomjs-prebuilt": "^2.1.16",
"rimraf": "^2.6.2",
"sass-loader": "^6.0.7",
"sinon": "^4.4.2",
"style-loader": "^0.19.1",
"tfx-cli": "^0.4.16",
"ts-loader": "^3.5.0",
"typescript": "^2.7.2",
"typings": "^2.1.1",
"webpack": "^3.11.0",
"webpack-dev-server": "^2.11.2"
"tfx-cli": "^0.6.3",
"typescript": "^3.2.1"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc -p .",
"package": "tfx extension create"
},
"dependencies": {
"vss-web-extension-sdk": "^2.117.0"
"vss-web-extension-sdk": "^5.141.0"
}
}

Просмотреть файл

@ -5,16 +5,16 @@ WSJF enables a **calculated field** for computing and storing WSJF on your work
The [Scaled Agile Framework](http://www.scaledagileframework.com) defines [WSJF (Weighted Shortest Job First)](http://www.scaledagileframework.com/wsjf/) as a calculation of cost of delay vs. job size which can help teams prioritize their portfolio backlogs with the items contributing the highest ROI.
![WSJF = (Business Value + Time Criticality)/Job Size](http://www.scaledagileframework.com/wp-content/uploads/2014/07/Figure-2.-A-formula-for-calculating-WSJF.png)
![WSJF = (Business Value + Time Criticality - Risk Reduction | Opportunity Enablement Value)/Job Size](http://www.scaledagileframework.com/wp-content/uploads/2014/07/Figure-2.-A-formula-for-calculating-WSJF.png)
Three values are used to calculate WSJF:
Four values are used to calculate WSJF:
* **Business Value**
* **Risk Reduction | Opportunity Enablement Value**
* **Time Criticality**
* **Job Size**
# Setup
1. The first thing you need is to create the field that will store the WSJF values. [Create a custom decimal field](https://www.visualstudio.com/en-us/docs/work/process/customize-process-field#add-a-custom-field) through the process hub and add it to the work items you want to display WSJF data on.
1. The first thing you need is to create the fields that will store the RR-OE and WSJF values. [Create a custom decimal field](https://www.visualstudio.com/en-us/docs/work/process/customize-process-field#add-a-custom-field) through the process hub and add it to the work items you want to display WSJF data on.
![WSJF displaying on the work item form](marketplace/CreateField.png)
*NOTE: If you're using TFS onprem, you need to use witadmin to [Create a custom decimal field](https://www.visualstudio.com/en-us/docs/work/customize/add-modify-field#to-add-a-custom-field)*
@ -37,65 +37,20 @@ Three values are used to calculate WSJF:
![Mapping fields for calculation](marketplace/Settings.gif)
## Support
Because this extension requires the new work item form, it is only supported on VSTS and the next version of TFS (Dev15-RC1).
# Using the sample
This is an example for using relatively modern web dev technologies to build a VSTS (https://www.visualstudio.com) extension. In contrast to my other seed project and example (https://github.com/cschleiden/vsts-extension-ts-seed-simple and https://github.com/cschleiden/vsts-extension-tags-mru) which focused on simplicity, this sample aims to be more complete. It supports:
- Code written in Typescript/Styling defined using SASS
- Publishing a dev version of an extension and a production one, without changing the manifest
- Webpack for watching and building files during development, and for building optimized bundles for production
- Unit tests of the core logic using mocha/chai
- React for rendering a complex UI with user interation
## Building ##
This extension uses *webpack* for bundling, *webpack-dev-server* for watching files and serving bundles during development, *mocha*, *chai* for writing unit tests, and *karma* as a test runner.
Two bundles are defined for webpack, one for the main dialog, one for the extension context menu registration.
All actions can be triggered using npm scripts (`npm run <target>`), no additional task runner required.
### General setup ###
You need
* node/npm
1. Download the .zip file from the [Master Branch](https://github.com/Microsoft/vsts-wsjf-extension/archive/master.zip)
2. Extract to a local folder on your machine
3. Open up a command prompt (or powershell) and change directory to your root folder *(ie. C:\ ... \vsts-wsjf-extension-master)*
4. Run the command in command prompt: `npm update && npm install`
*Note: Be sure to install npm in your root directory*
### Development (Local Instance of TFS) ###
*These are solutions for running on a local instance of TFS **only**. If you build from these and push to a live server, they will not work*
* Run `npm run publish:dev` to publish the current extension manifest to the marketplace as a private extension with a suffix of `-dev` added to the extension id. This package will use a baseUri of `https://localhost:8080`.
* Run `npm run dev` to start a webpack development server that watches all source files. Tests live next to product code and use a `.tests.ts` suffix instead of only `.ts`.
* To run a single test pass execute `npm run test`, to keep watching tests and build/execute as you develop execute `npm run dev:test`.
### Production (Live TFS Server) ###
*Pushing to a live TFS Server instance*
1. Run `npm run publish:release` to compile all modules into bundles, package them into a .vsix, and publish as a *public* extension to the VSTS marketplace.
Because this extension requires the new work item form, it is only supported on Azure DevOps and the next version of TFS 2018 and above.
*NOTE: You may get an error on the publish part, however your .vsix file should be in your root to manually upload to your TFS Server if you have not setup a marketplace*
### Adding RROE and WSJF Score Values (For TFS) ###
### Adding RROE and WSJF Score Values (For Non-VSTS) ###
1. Export your WorkItem.XML file *(ie. Epic.XML)* using either [Visual Studio Powertools](https://marketplace.visualstudio.com/items?itemName=VisualStudioProductTeam.ProductivityPowerPack2017) or [WITAdmin](https://docs.microsoft.com/en-us/vsts/work/customize/reference/witadmin/witadmin-import-export-manage-wits?view=tfs-2018)
1. Export your WorkItem.XML file *(ie. Epic.XML)* using [WITAdmin](https://docs.microsoft.com/en-us/Azure DevOps/work/customize/reference/witadmin/witadmin-import-export-manage-wits?view=tfs-2018)
2. At the bottom of your "Fields" section add the following (Name and reference names may vary):
``` xml
<FIELD name="WSJF Risk-Reduction Opportunity-Enablement" refname="WSFJ.RROEValue" type="Integer" reportable="dimension">
<HELPTEXT> WSJF Risk-Reduction </HELPTEXT>
<FIELD name="WSJF Risk-Reduction Opportunity-Enablement" refname="WSJF.RROEValue" type="Integer" reportable="dimension">
<HELPTEXT>WSJF Risk-Reduction</HELPTEXT>
</FIELD>
<FIELD name="WSJF Score" refname="WSJF.Score" type="Double" reportable="dimension">
<HELPTEXT> WSJF Score </HELPTEXT>
<HELPTEXT>WSJF Score</HELPTEXT>
</FIELD>
```
3. Under your
@ -104,17 +59,13 @@ You need
```xml
<Section>
<Group Label="WSJF">
<Control Label="User-Business Value" Type="FieldControl" FieldName="Microsoft.VSTS.Common.BusinessValue" EmptyText="[Numbered Value]" />
<Control Label="Urgency/Time Criticality" Type="FieldControl" FieldName="Microsoft.VSTS.Common.TimeCriticality" EmptyText="[Numbered Value]" />
<Control Label="User-Business Value" Type="FieldControl" FieldName="Microsoft.Azure DevOps.Common.BusinessValue" EmptyText="[Numbered Value]" />
<Control Label="Urgency/Time Criticality" Type="FieldControl" FieldName="Microsoft.Azure DevOps.Common.TimeCriticality" EmptyText="[Numbered Value]" />
<Control Label="Risk Reduction/Opportunity Enablement" Type="FieldControl" FieldName="WSJF.RROEValue" EmptyText="[Numbered Value]" />
<Control Label="Size" Type="FieldControl" FieldName="Microsoft.VSTS.Scheduling.Effort" EmptyText="[Numbered Value]" />
<Control Label="Size" Type="FieldControl" FieldName="Microsoft.Azure DevOps.Scheduling.Effort" EmptyText="[Numbered Value]" />
<Control Label="WSJF Score" Type="FieldControl" FieldName="WSJF.Value" EmptyText="[Numbered Value]" />
</Group>
</Section>
```
4. After this is done, open up your WSJF tab and adjust your settings:
![Mapping fields for calculation](marketplace/Settings.png)

Просмотреть файл

@ -1,11 +0,0 @@
var exec = require("child_process").exec;
// Load existing publisher
var manifest = require("../vss-extension.json");
var extensionId = manifest.id;
// Package extension
var command = `tfx extension create --overrides-file configs/dev.json --manifest-globs vss-extension.json --extension-id ${extensionId}-dev --no-prompt`;
exec(command, function() {
console.log("Package created");
});

Просмотреть файл

@ -1,12 +0,0 @@
var exec = require("child_process").exec;
var manifest = require("../vss-extension.json");
var extensionId = manifest.id;
var extensionPublisher = manifest.publisher;
var extensionVersion = manifest.version;
// Package extension
var command = `tfx extension publish --vsix ${extensionPublisher}.${extensionId}-dev-${extensionVersion}.vsix --no-prompt`;
exec(command, function() {
console.log("Package published.");
});

Просмотреть файл

@ -1,52 +0,0 @@
//"use strict";
var exec = require("child_process").exec;
//var cwd = require('cwd');
//var path = require('path');
// Package extension
var command = `tfx extension create --overrides-file release.json --manifest-globs vss-extension-release.json --no-prompt --json`;
//var pkg = File.join(cwd, '');
//var test = `system("cd")`;
//exec(test, stdout => {
// console.log(`${stdout}`);
//});
//var outputPath = "";
//exec('cd', (err, stdout, stderr) => {
// if (err) {
// // node couldn't execute the command
// return;
// }
// // the *entire* stdout and stderr (buffered)
// console.log(`stdout: ${stdout}`);
// console.log(`stderr: ${stderr}`);
// outputPath = `${stdout}/dist`
//});
exec(command, {
"cwd": "./dist"
// outputPath
}, (error, stdout) => {
if (error) {
console.error(`Could not create package: '${error}'`);
return;
}
let output = JSON.parse(stdout);
console.log(`Package created ${output.path}`);
var command = `tfx extension publish --vsix ${output.path} --no-prompt`;
exec(command, (error, stdout) => {
if (error) {
console.error(`Could not create package: '${error}'`);
return;
}
console.log("Package published.");
});
});

Просмотреть файл

@ -1,8 +1,10 @@
import Q = require("q");
import WIT_Client = require("TFS/WorkItemTracking/RestClient");
import Contracts = require("TFS/WorkItemTracking/Contracts");
import {IWorkItemFormService, WorkItemFormService} from "TFS/WorkItemTracking/Services";
import { StoredFieldReferences } from "wsjfModels";
import TFS_Wit_Contracts = require("TFS/WorkItemTracking/Contracts");
import TFS_Wit_Client = require("TFS/WorkItemTracking/RestClient");
import TFS_Wit_Services = require("TFS/WorkItemTracking/Services");
import { StoredFieldReferences } from "./wsjfModels";
function GetStoredFields(): IPromise<any> {
var deferred = Q.defer();
@ -22,12 +24,12 @@ function GetStoredFields(): IPromise<any> {
function getWorkItemFormService()
{
return WorkItemFormService.getService();
return TFS_Wit_Services.WorkItemFormService.getService();
}
function updateWSJFOnForm(storedFields:StoredFieldReferences) {
getWorkItemFormService().then((service) => {
service.getFields().then((fields: Contracts.WorkItemField[]) => {
service.getFields().then((fields: TFS_Wit_Contracts.WorkItemField[]) => {
var matchingBusinessValueFields = fields.filter(field => field.referenceName === storedFields.bvField);
var matchingTimeCriticalityFields = fields.filter(field => field.referenceName === storedFields.tcField);
var matchingRROEValueFields = fields.filter(field => field.referenceName === storedFields.rvField);
@ -69,8 +71,8 @@ function updateWSJFOnGrid(workItemId, storedFields:StoredFieldReferences):IPromi
var deferred = Q.defer();
var client = WIT_Client.getClient();
client.getWorkItem(workItemId, wsjfFields).then((workItem: Contracts.WorkItem) => {
var client = TFS_Wit_Client.getClient();
client.getWorkItem(workItemId, wsjfFields).then((workItem: TFS_Wit_Contracts.WorkItem) => {
if (storedFields.wsjfField !== undefined && storedFields.rvField !== undefined) {
var businessValue = +workItem.fields[storedFields.bvField];
var timeCriticality = +workItem.fields[storedFields.tcField];
@ -91,7 +93,7 @@ function updateWSJFOnGrid(workItemId, storedFields:StoredFieldReferences):IPromi
// Only update the work item if the WSJF has changed
if (wsjf != workItem.fields[storedFields.wsjfField]) {
client.updateWorkItem(document, workItemId).then((updatedWorkItem:Contracts.WorkItem) => {
client.updateWorkItem(document, workItemId).then((updatedWorkItem:TFS_Wit_Contracts.WorkItem) => {
deferred.resolve(updatedWorkItem);
});
}

Просмотреть файл

Просмотреть файл

@ -1,5 +1,3 @@
import "./wsjfSettings.scss";
import Q = require("q");
import Controls = require("VSS/Controls");
import {Combo, IComboOptions} from "VSS/Controls/Combos";
@ -7,7 +5,8 @@ import Menus = require("VSS/Controls/Menus");
import WIT_Client = require("TFS/WorkItemTracking/RestClient");
import Contracts = require("TFS/WorkItemTracking/Contracts");
import Utils_string = require("VSS/Utils/String");
import { StoredFieldReferences } from "wsjfModels";
import { StoredFieldReferences } from "./wsjfModels";
export class Settings {
private _changeMade = false;
@ -90,7 +89,7 @@ export class Settings {
header = $("<div />").addClass("description-text bowtie").appendTo(hubContent);
header.html(Utils_string.format(descriptionText));
$("<img src='http://www.scaledagileframework.com/wp-content/uploads/2014/07/Figure-2.-A-formula-for-calculating-WSJF.png' />").addClass("description-image").appendTo(hubContent);
$("<img src='https://www.scaledagileframework.com/wp-content/uploads/2014/07/Figure-2.-A-formula-for-calculating-WSJF.png' />").addClass("description-image").appendTo(hubContent);
descriptionText = "You must add a custom decimal field from the {0} to each work item type you wish to compute WSJF.";
header = $("<div />").addClass("description-text bowtie").appendTo(hubContent);

Просмотреть файл

@ -1,11 +1,15 @@
{
"compilerOptions": {
"module": "amd",
"sourceMap": false
"moduleResolution": "node",
"sourceMap": false,
"jsx": "react",
"outDir": "dist",
"types": [
"vss-web-extension-sdk"
]
},
"filesGlob": [
"src/**/*.ts",
"src/**/*.tsx",
"typings/index.d.ts"
"include": [
"src/**/*.tsx"
]
}

Просмотреть файл

@ -1,13 +0,0 @@
{
"globalDependencies": {
"jquery": "registry:dt/jquery#1.10.0+20160316155526",
"knockout": "registry:dt/knockout#0.0.0+20160409112158",
"mocha": "registry:env/mocha#2.2.5+20160321223601",
"q": "registry:dt/q#0.0.0+20160323171452",
"tfs": "npm:vss-web-extension-sdk/typings/tfs.d.ts",
"vss": "npm:vss-web-extension-sdk/typings/vss.d.ts"
},
"dependencies": {
"chai": "registry:npm/chai#3.5.0+20160415060238"
}
}

Просмотреть файл

@ -1,47 +1,23 @@
{
"public": false,
"manifestVersion": 1,
"version": "1.0.0",
"name": "WSJF (Weighted Shortest Job First)",
"scopes": [ "vso.work", "vso.work_write" ],
"description": "Auto calculates WSJF (weighted shortest job first) per work item and stores it in a work item field.",
"publisher": "MS-Agile-SAFe",
"id": "WSJF-extension",
"icons": {
"default": "marketplace/logo.png"
},
"publisher": "MS-Agile-SAFe",
"version": "1.2.0",
"name": "WSJF (Weighted Shortest Job First)",
"description": "Auto calculates WSJF (weighted shortest job first) per work item and stores it in a work item field.",
"categories": [ "Azure Boards" ],
"tags": [
"WSJF",
"SAFe"
],
"targets": [
{
"id": "Microsoft.VisualStudio.Services"
}
],
"tags": [
"Work Items", "WSJF", "SAFe", "Agile", "Backlog", "Stack rank"
],
"content": {
"details": {
"path": "marketplace/details.md"
}
},
"links": {
"learn": {
"uri": "https://github.com/Microsoft/vsts-wsjf-extension"
},
"support": {
"uri": "mailto:jmarks@microsoft.com"
}
},
"branding": {
"color": "rgb(220, 235, 252)",
"theme": "light"
},
"categories": [
"Plan and track"
],
"demands" : [
"api-version/3.0"
],
"contributions": [
{
{
"id": "wsjf-work-item-form-observer",
"type": "ms.vss-work-web.work-item-notifications",
"description": "Update the 'WSJF' field when other fields on the form change.",
@ -49,7 +25,7 @@
"ms.vss-work-web.work-item-form"
],
"properties": {
"uri": "src/wsjf.html"
"uri": "wsjf.html"
}
},
{
@ -61,7 +37,7 @@
],
"properties": {
"name": "WSJF",
"uri": "src/wsjfSettings.html"
"uri": "wsjfSettings.html"
}
},
{
@ -73,12 +49,67 @@
],
"properties": {
"text": "Recalculate WSJF values",
"title": "Update the WSJF value for the selected work items",
"icon": "img/icon-refresh.png",
"title": "Update the WSJF value for the selected work items",
"icon": "images/icon-refresh.png",
"groupId": "SAFe",
"uri": "src/wsjf.html"
"uri": "wsjf.html"
}
}
],
"files": []
"files": [
{
"path": "css",
"addressable": true
},
{
"path": "images",
"addressable": true
},
{
"path": "dist",
"addressable": true,
"packagePath": "scripts"
},
{
"path": "wsjf.html",
"addressable": true
},
{
"path": "wsjfSettings.html",
"addressable": true
},
{
"path": "node_modules/vss-web-extension-sdk/lib",
"addressable": true,
"packagePath": "lib"
}
],
"scopes": [
"vso.work",
"vso.work_write"
],
"icons": {
"default": "images/logo.png"
},
"content": {
"details": {
"path": "marketplace/details.md"
}
},
"links": {
"support": {
"uri": "mailto:jmarks@microsoft.com"
}
},
"repository": {
"type": "git",
"uri": "https://github.com/Microsoft/vsts-wsjf-extension"
},
"branding": {
"color": "rgb(220, 235, 252)",
"theme": "light"
},
"demands": [
"api-version/3.0"
]
}

Просмотреть файл

@ -1,48 +0,0 @@
var path = require("path");
var webpack = require("webpack");
var CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
target: "web",
entry: {
wsjf: "./src/wsjf.tsx",
wsjfSettings: "./src/wsjfSettings.tsx"
},
output: {
filename: "src/[name].js",
libraryTarget: "amd"
},
externals: [
/^VSS\/.*/, /^TFS\/.*/, /^q$/
],
resolve: {
extensions: [
".webpack.js",
".web.js",
".ts",
".tsx",
".js"]
},
module: {
loaders: [
{
test: /\.tsx?$/,
loader: "ts-loader"
},
{
test: /\.s?css$/,
loaders: ["style-loader", "css-loader", "sass-loader"]
}
]
},
plugins: [
new CopyWebpackPlugin([
{ from: "./node_modules/vss-web-extension-sdk/lib/VSS.SDK.min.js", to: "libs/VSS.SDK.min.js" },
{ from: "./src/*.html", to: "./" },
{ from: "./marketplace", to: "marketplace" },
{ from: "./img", to: "img" },
{ from: "./vss-extension.json", to: "vss-extension-release.json" },
{ from: "./configs/release.json", to: "release.json" }
])
]
}

Просмотреть файл

@ -3,19 +3,18 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<script src="../libs/VSS.SDK.min.js"></script>
<script src="lib/VSS.SDK.min.js"></script>
</head>
<body>
<script type="text/javascript">
// Initialize framework
VSS.init({
explicitNotifyLoaded: true,
usePlatformScripts: true,
configureModuleLoader: true
usePlatformScripts: true
});
// Load main entry point for extension
VSS.require(["src/wsjf"], function () {
VSS.require(["scripts/wsjf"], function () {
// Loading succeeded
VSS.notifyLoadSucceeded();
});

Просмотреть файл

@ -3,19 +3,20 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<script src="../libs/VSS.SDK.min.js"></script>
<script src="lib/VSS.SDK.min.js"></script>
<link href="css/wsjfSettings.css" rel="stylesheet"></link>
</head>
<body>
<script type="text/javascript">
// Initialize framework
VSS.init({
explicitNotifyLoaded: true,
usePlatformScripts: true,
configureModuleLoader: true
usePlatformScripts: true
});
// Load main entry point for extension
VSS.require(["src/wsjfSettings"], function (s) {
VSS.require(["scripts/wsjfSettings"], function (s) {
var settings = new s.Settings();
settings.initialize();
});
@ -25,6 +26,6 @@
<div class="hub-title">Settings</div>
<div class="hub-content" />
</div>
</div>
</body>
</html>
</html>