├── src ├── configs │ ├── release.json │ └── dev.json ├── img │ ├── one-click.png │ ├── screen01.png │ ├── screen02.png │ ├── screen03.png │ └── one-click16.png ├── package.json ├── toolbar.html ├── gruntfile.js ├── vss-extension.json ├── overview.md ├── lib │ └── VSS.SDK.min.js └── scripts │ └── app.js ├── dist └── ruifig.vsts-work-item-one-click-child-links-0.12.1.vsix ├── LICENSE ├── readme.md └── .gitignore /src/configs/release.json: -------------------------------------------------------------------------------- 1 | { 2 | "public": true 3 | } -------------------------------------------------------------------------------- /src/img/one-click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/figueiredorui/1-click-child-links/HEAD/src/img/one-click.png -------------------------------------------------------------------------------- /src/img/screen01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/figueiredorui/1-click-child-links/HEAD/src/img/screen01.png -------------------------------------------------------------------------------- /src/img/screen02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/figueiredorui/1-click-child-links/HEAD/src/img/screen02.png -------------------------------------------------------------------------------- /src/img/screen03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/figueiredorui/1-click-child-links/HEAD/src/img/screen03.png -------------------------------------------------------------------------------- /src/img/one-click16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/figueiredorui/1-click-child-links/HEAD/src/img/one-click16.png -------------------------------------------------------------------------------- /src/configs/dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "vsts-work-item-one-click-child-links-dev", 3 | "name": "1-Click Child-Links", 4 | "public": false 5 | } -------------------------------------------------------------------------------- /dist/ruifig.vsts-work-item-one-click-child-links-0.12.1.vsix: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/figueiredorui/1-click-child-links/HEAD/dist/ruifig.vsts-work-item-one-click-child-links-0.12.1.vsix -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "grunt": "^1.0.4", 4 | "grunt-cli": "^1.2.0", 5 | "grunt-contrib-clean": "^1.0.0", 6 | "grunt-contrib-copy": "~1.0.0", 7 | "grunt-exec": "~0.4.7", 8 | "requirejs": "^2.2.0", 9 | "tfx-cli": "^0.8.1", 10 | "vss-web-extension-sdk": "^1.104.0" 11 | }, 12 | "name": "vsts-work-item-one-click-child-links", 13 | "private": true, 14 | "version": "0.10.1", 15 | "scripts": {} 16 | } 17 | -------------------------------------------------------------------------------- /src/toolbar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Rui 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.initConfig({ 3 | exec: { 4 | package_dev: { 5 | command: "tfx extension create --rev-version --manifests vss-extension.json --overrides-file configs/dev.json --output-path ../dist" , 6 | stdout: true, 7 | stderr: true 8 | }, 9 | package_release: { 10 | command: "tfx extension create --manifests vss-extension.json --overrides-file configs/release.json --output-path ../dist", 11 | stdout: true, 12 | stderr: true 13 | }, 14 | publish_dev: { 15 | command: "tfx extension publish --service-url https://marketplace.visualstudio.com --manifests vss-extension.json --overrides-file configs/dev.json --output-path ../dist", 16 | stdout: true, 17 | stderr: true 18 | }, 19 | publish_release: { 20 | command: "tfx extension publish --service-url https://marketplace.visualstudio.com --manifests vss-extension.json --overrides-file configs/release.json --output-path ../dist", 21 | stdout: true, 22 | stderr: true 23 | } 24 | }, 25 | copy: { 26 | scripts: { 27 | files: [{ 28 | expand: true, 29 | flatten: true, 30 | src: ["node_modules/vss-web-extension-sdk/lib/VSS.SDK.min.js"], 31 | dest: "lib", 32 | filter: "isFile" 33 | }] 34 | } 35 | }, 36 | 37 | clean: ["../dist/*.vsix"], 38 | 39 | 40 | }); 41 | 42 | grunt.loadNpmTasks("grunt-exec"); 43 | grunt.loadNpmTasks("grunt-contrib-copy"); 44 | grunt.loadNpmTasks('grunt-contrib-clean'); 45 | 46 | grunt.registerTask("package-dev", ["exec:package_dev"]); 47 | grunt.registerTask("package-release", ["exec:package_release"]); 48 | grunt.registerTask("publish-dev", ["package-dev", "exec:publish_dev"]); 49 | grunt.registerTask("publish-release", ["package-release", "exec:publish_release"]); 50 | 51 | grunt.registerTask("default", ["package-dev"]); 52 | }; -------------------------------------------------------------------------------- /src/vss-extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifestVersion": 1.0, 3 | "id": "vsts-work-item-one-click-child-links", 4 | "version": "0.12.1", 5 | "name": "1-Click Child-Links", 6 | "description": "Add Child-Links from pre-defined templates with a single click.", 7 | "public": false, 8 | "publisher": "ruifig", 9 | "repository": { 10 | "type": "git", 11 | "uri": "https://github.com/figueiredorui/1-click-child-links" 12 | }, 13 | "icons": { 14 | "default": "img/one-click.png" 15 | }, 16 | "screenshots": [ 17 | { 18 | "path": "img/screen01.png" 19 | }, 20 | { 21 | "path": "img/screen02.png" 22 | }, 23 | { 24 | "path": "img/screen03.png" 25 | } 26 | ], 27 | "tags": [ 28 | "Tasks" 29 | ], 30 | "categories": [ 31 | "Plan and track" 32 | ], 33 | "content": { 34 | "details": { 35 | "path": "overview.md" 36 | } 37 | }, 38 | "targets": [ 39 | { 40 | "id": "Microsoft.VisualStudio.Services" 41 | } 42 | ], 43 | "scopes": [ 44 | "vso.work", 45 | "vso.work_write" 46 | ], 47 | "files": [ 48 | { 49 | "path": "img", 50 | "addressable": true 51 | }, 52 | { 53 | "path": "toolbar.html", 54 | "addressable": true 55 | }, 56 | { 57 | "path": "scripts/app.js", 58 | "addressable": true 59 | }, 60 | { 61 | "path": "lib/VSS.SDK.min.js", 62 | "addressable": true 63 | } 64 | ], 65 | "contributions": [ 66 | { 67 | "id": "create-child-task-work-item-button", 68 | "type": "ms.vss-web.action", 69 | "description": "1-Click Child-Links", 70 | "targets": [ 71 | "ms.vss-work-web.work-item-toolbar-menu", 72 | "ms.vss-work-web.backlog-item-menu", 73 | "ms.vss-work-web.work-item-context-menu" 74 | ], 75 | "properties": { 76 | "text": " 1-Click Child-Links", 77 | "title": "1-Click Child-Links", 78 | "toolbarText": "1-Click Child-Links", 79 | "icon": "img/one-click16.png", 80 | "uri": "toolbar.html", 81 | "registeredObjectId": "create-child-task-work-item-button" 82 | } 83 | } 84 | ] 85 | } -------------------------------------------------------------------------------- /src/overview.md: -------------------------------------------------------------------------------- 1 | ## 1-Click Child-Links ## 2 | 3 | 1-Click Child-Links is an Azure DevOps extension for creating multiple work items as children via single click, where each work item is based on a single pre-defined template. 4 | 5 | Azure DevOps offers team-specific work item templating as core functionality with which you can quickly apply pre-populated values for your team's commonly used fields per work item type. 6 | 7 | The child work items created by this extension are based on the hierarchy of work item types defined in the process template (Agile, Scrum, CMMI). 8 | 9 | For example, if you're using a process inherited from the agile template with a custom requirement-level type called defect and 3 task templates defined, using 1-click on a user story or defect will generate three child tasks, one for each defined template. 10 | 11 | It's also possible to limit which parent work items apply to each template in one of two ways: 12 | 13 | Simplified: put the list of applicable parent work item types in the child template's description field, like this: `[Product Backlog Item,Defect]` 14 | 15 | Complex: put a minified (single line) JSON string into the child template's description field, like this: 16 | 17 | ``` json 18 | { 19 | "applywhen": 20 | { 21 | "System.State": "Approved", 22 | "System.Tags" : ["Blah", "ClickMe"], 23 | "System.WorkItemType": "Product Backlog Item" 24 | } 25 | } 26 | ``` 27 | 28 | ### Define team templates ### 29 | 30 | Manage work item templates 31 | 32 | ![Export](img/screen01.png) 33 | 34 | ### Create / open a work item ### 35 | 36 | Find 1-Click Child-Links on toolbar menu 37 | 38 | ![Export](img/screen02.png) 39 | 40 | ### Done ### 41 | 42 | You should now have children associated with the open work item. 43 | 44 | ![Export](img/screen03.png) 45 | 46 | ## Release notes ## 47 | 48 | * v0.11.3 49 | * Fixed issue #62 50 | * Fixed issue #49 51 | 52 | * v0.11.0 53 | * Fixed issue #50 54 | * Fixed issue #48 55 | * Fixed issue #46 56 | * Fixed issue #45 57 | * Fixed issue #41 58 | * Fixed issue #37 59 | * Enhancement issue #49 60 | 61 | * v0.10.0 62 | * Template applicability criteria can be defined using complex JSON objects in the template description. 63 | 64 | * v0.8.0 65 | * Template sets can now be created on keywords in titles on top of Work Item Types 66 | * Inherit values from parent work item fields (wiki) 67 | * Copy field value from parent (wiki) 68 | 69 | * v0.6.0 70 | * 1-Click Child-Links option available when selecting multiple work items 71 | 72 | * v0.5.0 73 | * 1-Click Child-Links option available on Card and Backlog context menu. 74 | 75 | * v0.4.0 76 | * Identifier to distinguish templates sets to be added in a single click (wiki) 77 | 78 | * v0.3.0 79 | * Enforce correct order when adding child links to work item 80 | 81 | ## Contributors ## 82 | 83 | 84 | 85 | 86 | ## Learn more ## 87 | 88 | This extension is an enhanced version of 1-Click Tasks 89 | 90 | The source for this extension is on GitHub. Take, fork, and extend. 91 | 92 | Let's discuss issues and improvements. -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## 1-Click Child-Links ## 2 | 3 | 1-Click Child-Links is an Azure DevOps extension for creating multiple work items as children via single click, where each work item is based on a single pre-defined template. 4 | 5 | Azure DevOps offers team-specific work item templating as core functionality with which you can quickly apply pre-populated values for your team's commonly used fields per work item type. 6 | 7 | The child work items created by this extension are based on the hierarchy of work item types defined in the process template (Agile, Scrum, CMMI). 8 | 9 | For example, if you're using a process inherited from the agile template with a custom requirement-level type called defect and 3 task templates defined, using 1-click on a user story or defect will generate three child tasks, one for each defined template. 10 | 11 | It's also possible to limit which parent work items apply to each template in one of two ways: 12 | 13 | Simplified: put the list of applicable parent work item types in the child template's description field, like this: `[Product Backlog Item,Defect]` 14 | 15 | Complex: put a minified (single line) JSON string into the child template's description field, like this: 16 | 17 | ``` json 18 | { 19 | "applywhen": 20 | { 21 | "System.State": "Approved", 22 | "System.Tags" : ["Blah", "ClickMe"], 23 | "System.WorkItemType": "Product Backlog Item" 24 | } 25 | } 26 | ``` 27 | 28 | ### Define team templates ### 29 | 30 | Manage work item templates 31 | 32 | Define team templates 33 | 34 | ### Create / open a work item ### 35 | 36 | Find 1-Click Child-Links on toolbar menu 37 | 38 | 1-Click Child-Links on work item form menu 39 | 40 | ### Done ### 41 | 42 | You should now have children associated with the open work item. 43 | 44 | Done 45 | 46 | ## Release notes ## 47 | 48 | * v0.11.3 49 | * Fixed issue #62 50 | * Fixed issue #49 51 | 52 | 53 | * v0.11.0 54 | * Fixed issue #50 55 | * Fixed issue #48 56 | * Fixed issue #46 57 | * Fixed issue #45 58 | * Fixed issue #41 59 | * Fixed issue #37 60 | * Enhancement issue #49 61 | 62 | * v0.10.0 63 | * Template applicability criteria can be defined using complex JSON objects in the template description. 64 | 65 | * v0.8.0 66 | * Template sets can now be created on keywords in titles on top of Work Item Types 67 | * Inherit values from parent work item fields (wiki) 68 | * Copy field value from parent (wiki) 69 | 70 | * v0.6.0 71 | * 1-Click Child-Links option available when selecting multiple work items 72 | 73 | * v0.5.0 74 | * 1-Click Child-Links option available on Card and Backlog context menu. 75 | 76 | * v0.4.0 77 | * Identifier to distinguish templates sets to be added in a single click (wiki) 78 | 79 | * v0.3.0 80 | * Enforce correct order when adding child links to work item 81 | 82 | ## Usage ## 83 | 84 | 1. Clone the repository 85 | 1. `npm install` to install required local dependencies 86 | 2. `npm install -g grunt` to install a global copy of grunt (unless it's already installed) 87 | 2. `grunt` to build and package the application 88 | 89 | ### Grunt ### 90 | 91 | Basic `grunt` tasks are defined: 92 | 93 | * `package-dev` - Builds the development version of the vsix package 94 | * `package-release` - Builds the release version of the vsix package 95 | * `publish-dev` - Publishes the development version of the extension to the marketplace using `tfx-cli` 96 | * `publish-release` - Publishes the release version of the extension to the marketplace using `tfx-cli` 97 | 98 | Note: To avoid `tfx` prompting for your token when publishing, login in beforehand using `tfx login` and the service uri of ` https://marketplace.visualstudio.com`. 99 | 100 | ### Debugging your extension ### 101 | In order to debug the extension using Visual Studio or Browser Developer Tools and speed up the development without redeploying extension each time you change source code, you need change manifest adding baseUri property: 102 | 103 | ``` json 104 | { 105 | 106 | "baseUri": "https://localhost:5501", 107 | 108 | } 109 | ``` 110 | 111 | ## Contributors ## 112 | 113 | 114 | 115 | 116 | ## Credits ## 117 | 118 | Clone from https://github.com/cschleiden/vsts-extension-ts-seed-simple 119 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 2 | ## 3 | ## Ignore Visual Studio temporary files, build results, and 4 | ## files generated by popular Visual Studio add-ons. 5 | 6 | .vscode/ 7 | 8 | # User-specific files 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | x64/ 23 | x86/ 24 | bld/ 25 | [Bb]in/ 26 | [Oo]bj/ 27 | [Ll]og/ 28 | 29 | # Visual Studio 2015 cache/options directory 30 | .vs/ 31 | # Uncomment if you have tasks that create the project's static files in wwwroot 32 | #wwwroot/ 33 | 34 | # MSTest test Results 35 | [Tt]est[Rr]esult*/ 36 | [Bb]uild[Ll]og.* 37 | 38 | # NUNIT 39 | *.VisualState.xml 40 | TestResult.xml 41 | 42 | # Build Results of an ATL Project 43 | [Dd]ebugPS/ 44 | [Rr]eleasePS/ 45 | dlldata.c 46 | 47 | # DNX 48 | project.lock.json 49 | project.fragment.lock.json 50 | artifacts/ 51 | Properties/launchSettings.json 52 | 53 | *_i.c 54 | *_p.c 55 | *_i.h 56 | *.ilk 57 | *.meta 58 | *.obj 59 | *.pch 60 | *.pdb 61 | *.pgc 62 | *.pgd 63 | *.rsp 64 | *.sbr 65 | *.tlb 66 | *.tli 67 | *.tlh 68 | *.tmp 69 | *.tmp_proj 70 | *.log 71 | *.vspscc 72 | *.vssscc 73 | .builds 74 | *.pidb 75 | *.svclog 76 | *.scc 77 | 78 | # Chutzpah Test files 79 | _Chutzpah* 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opendb 86 | *.opensdf 87 | *.sdf 88 | *.cachefile 89 | *.VC.db 90 | *.VC.VC.opendb 91 | 92 | # Visual Studio profiler 93 | *.psess 94 | *.vsp 95 | *.vspx 96 | *.sap 97 | 98 | # TFS 2012 Local Workspace 99 | $tf/ 100 | 101 | # Guidance Automation Toolkit 102 | *.gpState 103 | 104 | # ReSharper is a .NET coding add-in 105 | _ReSharper*/ 106 | *.[Rr]e[Ss]harper 107 | *.DotSettings.user 108 | 109 | # JustCode is a .NET coding add-in 110 | .JustCode 111 | 112 | # TeamCity is a build add-in 113 | _TeamCity* 114 | 115 | # DotCover is a Code Coverage Tool 116 | *.dotCover 117 | 118 | # Visual Studio code coverage results 119 | *.coverage 120 | *.coveragexml 121 | 122 | # NCrunch 123 | _NCrunch_* 124 | .*crunch*.local.xml 125 | nCrunchTemp_* 126 | 127 | # MightyMoose 128 | *.mm.* 129 | AutoTest.Net/ 130 | 131 | # Web workbench (sass) 132 | .sass-cache/ 133 | 134 | # Installshield output folder 135 | [Ee]xpress/ 136 | 137 | # DocProject is a documentation generator add-in 138 | DocProject/buildhelp/ 139 | DocProject/Help/*.HxT 140 | DocProject/Help/*.HxC 141 | DocProject/Help/*.hhc 142 | DocProject/Help/*.hhk 143 | DocProject/Help/*.hhp 144 | DocProject/Help/Html2 145 | DocProject/Help/html 146 | 147 | # Click-Once directory 148 | publish/ 149 | 150 | # Publish Web Output 151 | *.[Pp]ublish.xml 152 | *.azurePubxml 153 | # TODO: Comment the next line if you want to checkin your web deploy settings 154 | # but database connection strings (with potential passwords) will be unencrypted 155 | *.pubxml 156 | *.publishproj 157 | 158 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 159 | # checkin your Azure Web App publish settings, but sensitive information contained 160 | # in these scripts will be unencrypted 161 | PublishScripts/ 162 | 163 | # NuGet Packages 164 | *.nupkg 165 | # The packages folder can be ignored because of Package Restore 166 | **/packages/* 167 | # except build/, which is used as an MSBuild target. 168 | !**/packages/build/ 169 | # Uncomment if necessary however generally it will be regenerated when needed 170 | #!**/packages/repositories.config 171 | # NuGet v3's project.json files produces more ignoreable files 172 | *.nuget.props 173 | *.nuget.targets 174 | 175 | # Microsoft Azure Build Output 176 | csx/ 177 | *.build.csdef 178 | 179 | # Microsoft Azure Emulator 180 | ecf/ 181 | rcf/ 182 | 183 | # Windows Store app package directories and files 184 | AppPackages/ 185 | BundleArtifacts/ 186 | Package.StoreAssociation.xml 187 | _pkginfo.txt 188 | 189 | # Visual Studio cache files 190 | # files ending in .cache can be ignored 191 | *.[Cc]ache 192 | # but keep track of directories ending in .cache 193 | !*.[Cc]ache/ 194 | 195 | # Others 196 | ClientBin/ 197 | ~$* 198 | *~ 199 | *.dbmdl 200 | *.dbproj.schemaview 201 | *.jfm 202 | *.pfx 203 | *.publishsettings 204 | node_modules/ 205 | orleans.codegen.cs 206 | 207 | # Since there are multiple workflows, uncomment next line to ignore bower_components 208 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 209 | bower_components/ 210 | 211 | # RIA/Silverlight projects 212 | Generated_Code/ 213 | 214 | # Backup & report files from converting an old project file 215 | # to a newer Visual Studio version. Backup files are not needed, 216 | # because we have git ;-) 217 | _UpgradeReport_Files/ 218 | Backup*/ 219 | UpgradeLog*.XML 220 | UpgradeLog*.htm 221 | 222 | # SQL Server files 223 | *.mdf 224 | *.ldf 225 | 226 | # Business Intelligence projects 227 | *.rdl.data 228 | *.bim.layout 229 | *.bim_*.settings 230 | 231 | # Microsoft Fakes 232 | FakesAssemblies/ 233 | 234 | # GhostDoc plugin setting file 235 | *.GhostDoc.xml 236 | 237 | # Node.js Tools for Visual Studio 238 | .ntvs_analysis.dat 239 | 240 | # Visual Studio 6 build log 241 | *.plg 242 | 243 | # Visual Studio 6 workspace options file 244 | *.opt 245 | 246 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 247 | *.vbw 248 | 249 | # Visual Studio LightSwitch build output 250 | **/*.HTMLClient/GeneratedArtifacts 251 | **/*.DesktopClient/GeneratedArtifacts 252 | **/*.DesktopClient/ModelManifest.xml 253 | **/*.Server/GeneratedArtifacts 254 | **/*.Server/ModelManifest.xml 255 | _Pvt_Extensions 256 | 257 | # Paket dependency manager 258 | .paket/paket.exe 259 | paket-files/ 260 | 261 | # FAKE - F# Make 262 | .fake/ 263 | 264 | # JetBrains Rider 265 | .idea/ 266 | *.sln.iml 267 | 268 | # CodeRush 269 | .cr/ 270 | 271 | # Python Tools for Visual Studio (PTVS) 272 | __pycache__/ 273 | *.pyc 274 | 275 | # Cake - Uncomment if you are using it 276 | # tools/ 277 | 278 | tmp/ -------------------------------------------------------------------------------- /src/lib/VSS.SDK.min.js: -------------------------------------------------------------------------------- 1 | //dependencies= 2 | // Copyright (C) Microsoft Corporation. All rights reserved. 3 | var XDM,VSS;(function(n){function u(){return new o}function s(){return Math.floor(Math.random()*(f-t)+t).toString(36)+Math.floor(Math.random()*(f-t)+t).toString(36)}var i,r,e;n.createDeferred=u;var o=function(){function n(){var n=this;this._resolveCallbacks=[];this._rejectCallbacks=[];this._isResolved=!1;this._isRejected=!1;this.resolve=function(t){n._resolve(t)};this.reject=function(t){n._reject(t)};this.promise={};this.promise.then=function(t,i){return n._then(t,i)}}return n.prototype._then=function(t,i){var u=this,r;return!t&&!i||this._isResolved&&!t||this._isRejected&&!i?this.promise:(r=new n,this._resolveCallbacks.push(function(n){u._wrapCallback(t,n,r,!1)}),this._rejectCallbacks.push(function(n){u._wrapCallback(i,n,r,!0)}),this._isResolved?this._resolve(this._resolvedValue):this._isRejected&&this._reject(this._rejectValue),r.promise)},n.prototype._wrapCallback=function(n,t,i,r){if(!n){r?i.reject(t):i.resolve(t);return}var u;try{u=n(t)}catch(f){i.reject(f);return}u===undefined?i.resolve(t):u&&typeof u.then=="function"?u.then(function(n){i.resolve(n)},function(n){i.reject(n)}):i.resolve(u)},n.prototype._resolve=function(n){if(this._isRejected||this._isResolved||(this._isResolved=!0,this._resolvedValue=n),this._isResolved&&this._resolveCallbacks.length>0){var t=this._resolveCallbacks.splice(0);window.setTimeout(function(){for(var i=0,r=t.length;i0){var t=this._rejectCallbacks.splice(0);window.setTimeout(function(){for(var i=0,r=t.length;it.MAX_XDM_DEPTH)||this._shouldSkipSerialization(n))return null;if(a=function(t,e,o){var s,c,l,a,v;try{s=t[o]}catch(y){}(c=typeof s,c!=="undefined")&&(l=-1,c==="object"&&(l=r.originalObjects.indexOf(s)),l>=0?(a=r.newObjects[l],a.__circularReferenceId||(a.__circularReferenceId=u++),e[o]={__circularReference:a.__circularReferenceId}):c==="function"?(v=h._nextProxyFunctionId++,e[o]={__proxyFunctionId:h._registerProxyFunction(s,n),__channelId:h._channelId}):c==="object"?e[o]=s&&s instanceof Date?{__proxyDate:s.getTime()}:h._customSerializeObject(s,i,r,u,f+1):o!=="__proxyFunctionId"&&(e[o]=s))},r||(r={newObjects:[],originalObjects:[]}),r.originalObjects.push(n),n instanceof Array)for(o=[],r.newObjects.push(o),e=0,c=n.length;e0&&(f=!0);lt=!0;f&&tt()}e||a?ht():w()})},0)}function tt(){var n={localStorage:JSON.stringify(u||{})};i.invokeRemoteMethod("updateSandboxedStorage","VSS.HostControl",[n])}function pt(n,t){var i;i=typeof n=="string"?[n]:n;t||(t=function(){});l?it(i,t):(r?e||(e=!0,s&&(s=!1,ht())):nt({usePlatformScripts:!0}),rt(function(){it(i,t)}))}function it(n,i){t.diagnostics.bundlingEnabled?window.require(["VSS/Bundling"],function(t){t.requireModules(n).spread(function(){i.apply(this,arguments)})}):window.require(n,i)}function rt(n){s?window.setTimeout(n,0):(f||(f=[]),f.push(n))}function wt(){i.invokeRemoteMethod("notifyLoadSucceeded","VSS.HostControl")}function ut(n){i.invokeRemoteMethod("notifyLoadFailed","VSS.HostControl",[n])}function ft(){return b}function bt(){return k}function et(){return c}function kt(){return d}function dt(n,t){return ot(n).then(function(n){return t||(t={}),t.webContext||(t.webContext=ft()),t.extensionContext||(t.extensionContext=et()),n.getInstance(n.id,t)})}function ot(t){var r=XDM.createDeferred();return n.ready(function(){i.invokeRemoteMethod("getServiceContribution","vss.hostManagement",[t]).then(function(n){var t=n;t.getInstance=function(t,i){return st(n,t,i)};r.resolve(t)},r.reject)}),r.promise}function gt(t){var r=XDM.createDeferred();return n.ready(function(){i.invokeRemoteMethod("getContributionsForTarget","vss.hostManagement",[t]).then(function(n){var t=[];n.forEach(function(n){var i=n;i.getInstance=function(t,i){return st(n,t,i)};t.push(i)});r.resolve(t)},r.reject)}),r.promise}function st(t,r,u){var f=XDM.createDeferred();return n.ready(function(){i.invokeRemoteMethod("getBackgroundContributionInstance","vss.hostManagement",[t,r,u]).then(f.resolve,f.reject)}),f.promise}function ni(n,t){i.getObjectRegistry().register(n,t)}function ti(n,t){return i.getObjectRegistry().getInstance(n,t)}function ii(){return i.invokeRemoteMethod("getAccessToken","VSS.HostControl")}function ri(){return i.invokeRemoteMethod("getAppToken","VSS.HostControl")}function ui(n,t){o||(o=document.getElementsByTagName("body").item(0));var r=typeof n=="number"?n:o.scrollWidth,u=typeof t=="number"?t:o.scrollHeight;i.invokeRemoteMethod("resize","VSS.HostControl",[r,u])}function ht(){var i=oi(t.webContext),f,g,n,s,o,b,k,nt,tt,d,u;if(window.__vssPageContext=t,window.__cultureInfo=t.microsoftAjaxConfig.cultureInfo,a!==!1&&t.coreReferences.stylesheets&&t.coreReferences.stylesheets.forEach(function(n){if(n.isCoreStylesheet){var t=document.createElement("link");t.href=h(n.url,i);t.rel="stylesheet";p(t,"head")}}),!e){l=!0;w();return}if(f=[],g=!1,t.coreReferences.scripts&&(t.coreReferences.scripts.forEach(function(n){if(n.isCoreModule){var r=!1,t=window;n.identifier==="JQuery"?r=!!t.jQuery:n.identifier==="JQueryUI"?r=!!(t.jQuery&&t.jQuery.ui&&t.jQuery.ui.version):n.identifier==="AMDLoader"&&(r=typeof t.define=="function"&&!!t.define.amd);r?g=!0:f.push({source:h(n.url,i)})}}),t.coreReferences.coreScriptsBundle&&!g&&(f=[{source:h(t.coreReferences.coreScriptsBundle.url,i)}]),t.coreReferences.extensionCoreReferences&&f.push({source:h(t.coreReferences.extensionCoreReferences.url,i)})),n={baseUrl:c.baseUri,contributionPaths:null,paths:{},shim:{}},r.moduleLoaderConfig&&(r.moduleLoaderConfig.baseUrl&&(n.baseUrl=r.moduleLoaderConfig.baseUrl),ei(r.moduleLoaderConfig,n),ct(r.moduleLoaderConfig,n)),t.moduleLoaderConfig&&(ct(t.moduleLoaderConfig,n),s=t.moduleLoaderConfig.contributionPaths,s))for(o in s)if(s.hasOwnProperty(o)&&!n.paths[o]&&(b=s[o].value,n.paths[o]=b.match("^https?://")?b:i+b,k=t.moduleLoaderConfig.paths,k)){nt=o+"/";tt=v(i,t.moduleLoaderConfig.baseUrl);for(d in k)fi(d,nt)&&(u=k[d],u.match("^https?://")||(u=u[0]==="/"?v(i,u):v(tt,u)),n.paths[d]=u)}window.__vssModuleLoaderConfig=n;f.push({content:"require.config("+JSON.stringify(n)+");"});y(f,0,function(){l=!0;w()})}function fi(n,t){return n&&n.length>=t.length?n.substr(0,t.length).localeCompare(t)===0:!1}function v(n,t){var i=n||"";return i[i.length-1]!=="/"&&(i+="/"),t&&(i+=t[0]==="/"?t.substr(1):t),i}function ei(n,t,i){var r,u;if(n.paths){t.paths||(t.paths={});for(r in n.paths)n.paths.hasOwnProperty(r)&&(u=n.paths[r],i&&(u=i(r,n.paths[r])),u&&(t.paths[r]=u))}}function ct(n,t){if(n.shim){t.shim||(t.shim={});for(var i in n.shim)n.shim.hasOwnProperty(i)&&(t.shim[i]=n.shim[i])}}function oi(n){var r=n.account||n.host,t=r.uri,i=r.relativeUri;return t&&i&&(t[t.length-1]!=="/"&&(t+="/"),i[i.length-1]!=="/"&&(i+="/"),t=t.substr(0,t.length-i.length)),t}function y(n,t,i){var f=this,r,u;if(t>=n.length){i.call(this);return}r=document.createElement("script");r.type="text/javascript";n[t].source?(u=n[t].source,r.src=u,r.addEventListener("load",function(){y.call(f,n,t+1,i)}),r.addEventListener("error",function(){ut("Failed to load script: "+u)}),p(r,"head")):n[t].content&&(r.textContent=n[t].content,p(r,"head"),y.call(this,n,t+1,i))}function p(n,t){var i=document.getElementsByTagName(t)[0];i||(i=document.createElement(t),document.appendChild(i));i.appendChild(n)}function h(n,t){var i=(n||"").toLowerCase();return i.substr(0,2)!=="//"&&i.substr(0,5)!=="http:"&&i.substr(0,6)!=="https:"&&(n=t+(i[0]==="/"?"":"/")+n),n}function w(){var t=this,n;s=!0;f&&(n=f,f=null,n.forEach(function(n){n.call(t)}))}var yt;n.VssSDKVersion=2;n.VssSDKRestVersion="3.0";var o,b,t,c,k,d,r,l=!1,e,a,s=!1,f,i=XDM.XDMChannelManager.get().addChannel(window.parent),u,lt=!1,g=function(){function n(){t&&t.call(this)}function i(){}var t;return Object.defineProperties(i.prototype,{getItem:{get:function(){return function(n){var t=this[""+n];return typeof t=="undefined"?null:t}}},setItem:{get:function(){return function(t,i){t=""+t;var u=this[t],r=""+i;u!==r&&(this[t]=r,n())}}},removeItem:{get:function(){return function(t){t=""+t;typeof this[t]!="undefined"&&(delete this[t],n())}}},clear:{get:function(){return function(){var r=Object.keys(this),t,i,u;if(r.length>0){for(t=0,i=r;t 0) { 27 | 28 | templateType.forEach(function (element) { 29 | templates.push(element) 30 | }, this); 31 | } 32 | }, this); 33 | return templates; 34 | }); 35 | } 36 | 37 | function getTemplate(id) { 38 | var witClient = _WorkItemRestClient.getClient(); 39 | return witClient.getTemplate(ctx.project.id, ctx.team.id, id); 40 | } 41 | 42 | function IsPropertyValid(taskTemplate, key) { 43 | if (taskTemplate.fields.hasOwnProperty(key) == false) { 44 | return false; 45 | } 46 | if (key.indexOf('System.Tags') >= 0) { //not supporting tags for now 47 | return false; 48 | } 49 | if (taskTemplate.fields[key].toLowerCase() == '@me') { //current identity is handled later 50 | return false; 51 | } 52 | if (taskTemplate.fields[key].toLowerCase() == '@currentiteration') { //current iteration is handled later 53 | return false; 54 | } 55 | 56 | return true; 57 | } 58 | 59 | function replaceReferenceToParentField(fieldValue, currentWorkItem) { 60 | var filters = fieldValue.match(/[^{\}]+(?=})/g); 61 | if (filters) { 62 | for (var i = 0; i < filters.length; i++) { 63 | var parentField = filters[i]; 64 | var parentValue = currentWorkItem[parentField]; 65 | 66 | fieldValue = fieldValue.replace('{' + parentField + '}', parentValue) 67 | } 68 | } 69 | return fieldValue; 70 | } 71 | 72 | function createWorkItemFromTemplate(currentWorkItem, taskTemplate, teamSettings) { 73 | var workItem = []; 74 | 75 | for (var key in taskTemplate.fields) { 76 | if (IsPropertyValid(taskTemplate, key)) { 77 | //if field value is empty copies value from parent 78 | if (taskTemplate.fields[key] == '') { 79 | if (currentWorkItem[key] != null) { 80 | workItem.push({ "op": "add", "path": "/fields/" + key, "value": currentWorkItem[key] }) 81 | } 82 | } 83 | else { 84 | var fieldValue = taskTemplate.fields[key]; 85 | //check for references to parent fields - {fieldName} 86 | fieldValue = replaceReferenceToParentField(fieldValue, currentWorkItem); 87 | 88 | workItem.push({ "op": "add", "path": "/fields/" + key, "value": fieldValue }) 89 | } 90 | } 91 | } 92 | 93 | // if template has no title field copies value from parent 94 | if (taskTemplate.fields['System.Title'] == null) 95 | workItem.push({ "op": "add", "path": "/fields/System.Title", "value": currentWorkItem['System.Title'] }) 96 | 97 | // if template has no AreaPath field copies value from parent 98 | if (taskTemplate.fields['System.AreaPath'] == null) 99 | workItem.push({ "op": "add", "path": "/fields/System.AreaPath", "value": currentWorkItem['System.AreaPath'] }) 100 | 101 | // if template has no IterationPath field copies value from parent 102 | // check if IterationPath field value is @currentiteration 103 | if (taskTemplate.fields['System.IterationPath'] == null) 104 | workItem.push({ "op": "add", "path": "/fields/System.IterationPath", "value": currentWorkItem['System.IterationPath'] }) 105 | else if (taskTemplate.fields['System.IterationPath'].toLowerCase() == '@currentiteration') 106 | workItem.push({ "op": "add", "path": "/fields/System.IterationPath", "value": teamSettings.backlogIteration.name + teamSettings.defaultIteration.path }) 107 | 108 | // check if AssignedTo field value is @me 109 | if (taskTemplate.fields['System.AssignedTo'] != null) { 110 | if (taskTemplate.fields['System.AssignedTo'].toLowerCase() == '@me') { 111 | workItem.push({ "op": "add", "path": "/fields/System.AssignedTo", "value": ctx.user.uniqueName }) 112 | } 113 | 114 | // if (taskTemplate.fields['System.AssignedTo'].toLowerCase() == '') { 115 | // if (WIT['System.AssignedTo'] != null) { 116 | // workItem.push({ "op": "add", "path": "/fields/System.AssignedTo", "value": currentWorkItem['System.AssignedTo'] }) 117 | // } 118 | // } 119 | } 120 | 121 | return workItem; 122 | } 123 | 124 | function createWorkItem(service, currentWorkItem, taskTemplate, teamSettings) { 125 | 126 | var witClient = _WorkItemRestClient.getClient(); 127 | 128 | var newWorkItem = createWorkItemFromTemplate(currentWorkItem, taskTemplate, teamSettings); 129 | 130 | witClient.createWorkItem(newWorkItem, VSS.getWebContext().project.name, taskTemplate.workItemTypeName) 131 | .then(function (response) { 132 | 133 | 134 | //Add relation 135 | if (service != null) { 136 | service.addWorkItemRelations([ 137 | { 138 | rel: "System.LinkTypes.Hierarchy-Forward", 139 | url: response.url, 140 | }]); 141 | //Save 142 | service.beginSaveWorkItem(function (response) { 143 | // saved 144 | }, function (error) { 145 | ShowDialog(" Error beginSaveWorkItem: " + error); 146 | WriteError("createWorkItem " + error); 147 | }); 148 | } else { 149 | //save using RestClient 150 | var workItemId = currentWorkItem['System.Id'] 151 | var document = [{ 152 | op: "add", 153 | path: '/relations/-', 154 | value: { 155 | rel: "System.LinkTypes.Hierarchy-Forward", 156 | url: response.url, 157 | attributes: { 158 | isLocked: false, 159 | } 160 | } 161 | }]; 162 | 163 | witClient.updateWorkItem(document, workItemId) 164 | .then(function (response) { 165 | var a = response; 166 | VSS.getService(VSS.ServiceIds.Navigation) 167 | .then(function (navigationService) { 168 | WriteTrace('updateWorkItem completed') 169 | setTimeout(function () { 170 | navigationService.reload(); 171 | }, 1000); 172 | }); 173 | }, function (error) { 174 | ShowDialog(" Error updateWorkItem: " + JSON.stringify(error)); 175 | WriteError("createWorkItem " + error); 176 | }); 177 | } 178 | }, function (error) { 179 | ShowDialog(" Error createWorkItem: " + JSON.stringify(error)); 180 | WriteError("createWorkItem " + error); 181 | }); 182 | } 183 | 184 | function AddTasksOnForm(service) { 185 | 186 | service.getId() 187 | .then(function (workItemId) { 188 | return AddTasks(workItemId, service) 189 | }); 190 | } 191 | 192 | function AddTasksOnGrid(workItemId) { 193 | 194 | return AddTasks(workItemId, null) 195 | } 196 | 197 | function AddTasks(workItemId, service) { 198 | 199 | var witClient = _WorkItemRestClient.getClient(); 200 | var workClient = workRestClient.getClient(); 201 | 202 | var team = { 203 | projectId: ctx.project.id, 204 | teamId: ctx.team.id 205 | }; 206 | 207 | workClient.getTeamSettings(team) 208 | .then(function (teamSettings) { 209 | // Get the current values for a few of the common fields 210 | witClient.getWorkItem(workItemId) 211 | .then(function (value) { 212 | var currentWorkItem = value.fields; 213 | 214 | currentWorkItem['System.Id'] = workItemId; 215 | 216 | var workItemType = currentWorkItem["System.WorkItemType"]; 217 | GetChildTypes(witClient, workItemType) 218 | .then(function (childTypes) { 219 | if (childTypes == null) 220 | return; 221 | // get Templates 222 | getTemplates(childTypes) 223 | .then(function (response) { 224 | if (response.length == 0) { 225 | ShowDialog('No ' + childTypes + ' templates found. Please add ' + childTypes + ' templates for the project team.'); 226 | return; 227 | } 228 | // Create children alphabetically. 229 | var templates = response.sort(SortTemplates); 230 | var chain = Q.when(); 231 | templates.forEach(function (template) { 232 | chain = chain.then(createChildFromTemplate(witClient, service, currentWorkItem, template, teamSettings)); 233 | }); 234 | return chain; 235 | 236 | }); 237 | }); 238 | }) 239 | }) 240 | } 241 | 242 | function createChildFromTemplate(witClient, service, currentWorkItem, template, teamSettings) { 243 | return function () { 244 | return getTemplate(template.id).then(function (taskTemplate) { 245 | // Create child 246 | if (IsValidTemplateWIT(currentWorkItem, taskTemplate)) { 247 | if (IsValidTemplateTitle(currentWorkItem, taskTemplate)) { 248 | createWorkItem(service, currentWorkItem, taskTemplate, teamSettings) 249 | } 250 | } 251 | }); 252 | }; 253 | } 254 | 255 | function IsValidTemplateWIT(currentWorkItem, taskTemplate) { 256 | 257 | WriteTrace("template: '" + taskTemplate.name + "'"); 258 | 259 | // If not empty, does the description have the old square bracket approach or new JSON? 260 | var jsonFilters = extractJSON(taskTemplate.description)[0]; 261 | if (IsJsonString(JSON.stringify(jsonFilters))) { 262 | // example JSON: 263 | // 264 | // { 265 | // "applywhen": [ 266 | // { 267 | // "System.State": "Approved", 268 | // "System.Tags" : ["Blah", "ClickMe"], 269 | // "System.WorkItemType": "Product Backlog Item" 270 | // }, 271 | // { 272 | // "System.State": "Approved", 273 | // "System.Tags" : ["Blah", "ClickMe"], 274 | // "System.WorkItemType": "Product Backlog Item" 275 | // } 276 | // ] 277 | // } 278 | 279 | WriteTrace("filter: '" + JSON.stringify(jsonFilters) + "'"); 280 | 281 | var rules = jsonFilters.applywhen; 282 | if (!Array.isArray(rules)) 283 | rules = new Array(rules); 284 | 285 | var matchRule = rules.some(filters => { 286 | 287 | var matchFilter = Object.keys(filters).every(function (prop) { 288 | 289 | var matchfield = matchField(prop, currentWorkItem, filters); 290 | WriteTrace(" - filter['" + prop + "'] : '" + filters[prop] + "' - wit['" + prop + "'] : '" + currentWorkItem[prop] + "' equal ? " + matchfield); 291 | return matchfield 292 | }); 293 | 294 | return matchFilter; 295 | }); 296 | 297 | return matchRule; 298 | 299 | 300 | } else { 301 | var filters = taskTemplate.description.match(/[^[\]]+(?=])/g); 302 | 303 | if (filters) { 304 | var isValid = false; 305 | for (var i = 0; i < filters.length; i++) { 306 | var found = filters[i].split(',').find(function (f) { return f.trim().toLowerCase() == currentWorkItem["System.WorkItemType"].toLowerCase() }); 307 | if (found) { 308 | isValid = true; 309 | break; 310 | } 311 | } 312 | return isValid; 313 | } else { 314 | return true; 315 | } 316 | } 317 | } 318 | 319 | function matchField(fieldName, currentWorkItem, filterObject) { 320 | try { 321 | if (currentWorkItem[fieldName] == null) 322 | return false; 323 | 324 | if (typeof (filterObject[fieldName]) === "undefined") 325 | return false; 326 | 327 | // convert it to array for easy compare 328 | var filterValue = filterObject[fieldName]; 329 | if (!Array.isArray(filterValue)) 330 | filterValue = new Array(String(filterValue)); 331 | 332 | var currentWorkItemValue = currentWorkItem[fieldName]; 333 | if (fieldName == "System.Tags") { 334 | currentWorkItemValue = currentWorkItem[fieldName].split("; "); 335 | } 336 | else { 337 | if (!Array.isArray(currentWorkItemValue)) 338 | currentWorkItemValue = new Array(String(currentWorkItemValue)); 339 | } 340 | 341 | 342 | var match = filterValue.some(i => { 343 | return currentWorkItemValue.findIndex(c => i.toLowerCase() === c.toLowerCase()) >= 0; 344 | }) 345 | 346 | return match; 347 | } 348 | catch (e) { 349 | WriteError('matchField ' + e); 350 | return false; 351 | } 352 | 353 | } 354 | 355 | function IsValidTemplateTitle(currentWorkItem, taskTemplate) { 356 | var jsonFilters = extractJSON(taskTemplate.description)[0]; 357 | var isJSON = IsJsonString(JSON.stringify(jsonFilters)); 358 | if (isJSON) { 359 | return true; 360 | } 361 | var filters = taskTemplate.description.match(/[^{\}]+(?=})/g); 362 | var curTitle = currentWorkItem["System.Title"].match(/[^{\}]+(?=})/g); 363 | if (filters) { 364 | var isValid = false; 365 | if (curTitle) { 366 | for (var i = 0; i < filters.length; i++) { 367 | if (curTitle.indexOf(filters[i]) > -1) { 368 | isValid = true; 369 | break; 370 | } 371 | } 372 | 373 | } 374 | return isValid; 375 | } else { 376 | return true; 377 | } 378 | 379 | } 380 | 381 | function findWorkTypeCategory(categories, workItemType) { 382 | for (category of categories) { 383 | var found = category.workItemTypes.find(function (w) { return w.name == workItemType; }); 384 | if (found != null) { 385 | return category; 386 | } 387 | } 388 | } 389 | 390 | function GetChildTypes(witClient, workItemType) { 391 | 392 | return witClient.getWorkItemTypeCategories(VSS.getWebContext().project.name) 393 | .then(function (response) { 394 | var categories = response; 395 | var category = findWorkTypeCategory(categories, workItemType); 396 | 397 | if (category != null) { 398 | var requests = []; 399 | var workClient = workRestClient.getClient(); 400 | 401 | var team = { 402 | projectId: ctx.project.id, 403 | teamId: ctx.team.id 404 | }; 405 | 406 | bugsBehavior = workClient.getTeamSettings(team).bugsBehavior; //Off, AsTasks, AsRequirements 407 | 408 | if (category.referenceName === 'Microsoft.EpicCategory') { 409 | return witClient.getWorkItemTypeCategory(VSS.getWebContext().project.name, 'Microsoft.FeatureCategory') 410 | .then(function (response) { 411 | var category = response; 412 | 413 | return category.workItemTypes.map(function (item) { return item.name; }); 414 | }); 415 | } else if (category.referenceName === 'Microsoft.FeatureCategory') { 416 | requests.push(witClient.getWorkItemTypeCategory(VSS.getWebContext().project.name, 'Microsoft.RequirementCategory')); 417 | if (bugsBehavior === 'AsRequirements') { 418 | requests.push(witClient.getWorkItemTypeCategory(VSS.getWebContext().project.name, 'Microsoft.BugCategory')); 419 | } 420 | } else if (category.referenceName === 'Microsoft.RequirementCategory') { 421 | requests.push(witClient.getWorkItemTypeCategory(VSS.getWebContext().project.name, 'Microsoft.TaskCategory')); 422 | requests.push(witClient.getWorkItemTypeCategory(VSS.getWebContext().project.name, 'Microsoft.TestCaseCategory')); 423 | if (bugsBehavior === 'AsTasks') { 424 | requests.push(witClient.getWorkItemTypeCategory(VSS.getWebContext().project.name, 'Microsoft.BugCategory')); 425 | } 426 | } else if (category.referenceName === 'Microsoft.BugCategory' && bugsBehavior === 'AsRequirements') { 427 | requests.push(witClient.getWorkItemTypeCategory(VSS.getWebContext().project.name, 'Microsoft.TaskCategory')); 428 | } else if (category.referenceName === 'Microsoft.TaskCategory') { 429 | requests.push(witClient.getWorkItemTypeCategory(VSS.getWebContext().project.name, 'Microsoft.TaskCategory')); 430 | } else if (category.referenceName == 'Microsoft.BugCategory') { 431 | requests.push(witClient.getWorkItemTypeCategory(VSS.getWebContext().project.name, 'Microsoft.TaskCategory')); 432 | } 433 | 434 | return Q.all(requests) 435 | .then(function (response) { 436 | var categories = response; 437 | 438 | var result = []; 439 | categories.forEach(function (category) { 440 | category.workItemTypes.forEach(function (workItemType) { 441 | result.push(workItemType.name); 442 | }); 443 | }); 444 | return result; 445 | }); 446 | 447 | 448 | } 449 | }); 450 | } 451 | 452 | function ShowDialog(message) { 453 | 454 | var dialogOptions = { 455 | title: "1-Click Child-Links", 456 | width: 300, 457 | height: 200, 458 | resizable: false, 459 | }; 460 | 461 | 462 | 463 | VSS.getService(VSS.ServiceIds.Dialog).then(function (dialogSvc) { 464 | 465 | dialogSvc.openMessageDialog(message, dialogOptions) 466 | .then(function (dialog) { 467 | // 468 | }, function (dialog) { 469 | // 470 | }); 471 | }); 472 | } 473 | 474 | function SortTemplates(a, b) { 475 | var nameA = a.name.toLowerCase(), nameB = b.name.toLowerCase(); 476 | if (nameA < nameB) //sort string ascending 477 | return -1; 478 | if (nameA > nameB) 479 | return 1; 480 | return 0; //default return value (no sorting) 481 | } 482 | 483 | function WriteTrace(msg) { 484 | console.log('1-Click Child-Links: ' + msg); 485 | } 486 | 487 | function WriteLog(msg) { 488 | console.log('1-Click Child-Links: ' + msg); 489 | } 490 | 491 | function WriteError(msg) { 492 | console.error('1-Click Child-Links: ' + msg); 493 | } 494 | 495 | function extractJSON(str) { 496 | var firstOpen, firstClose, candidate; 497 | firstOpen = str.indexOf('{', firstOpen + 1); 498 | 499 | if (firstOpen != -1) { 500 | do { 501 | firstClose = str.lastIndexOf('}'); 502 | 503 | if (firstClose <= firstOpen) { 504 | return null; 505 | } 506 | do { 507 | candidate = str.substring(firstOpen, firstClose + 1); 508 | 509 | try { 510 | var res = JSON.parse(candidate); 511 | 512 | return [res, firstOpen, firstClose + 1]; 513 | } 514 | catch (e) { 515 | WriteError('extractJSON ...failed ' + e); 516 | } 517 | firstClose = str.substr(0, firstClose).lastIndexOf('}'); 518 | } while (firstClose > firstOpen); 519 | firstOpen = str.indexOf('{', firstOpen + 1); 520 | } while (firstOpen != -1); 521 | } else { return ''; } 522 | } 523 | 524 | function IsJsonString(str) { 525 | try { 526 | JSON.parse(str); 527 | } catch (e) { 528 | return false; 529 | } 530 | return true; 531 | } 532 | 533 | 534 | 535 | function arraysEqual(a, b) { 536 | if (a === b) return true; 537 | if (a == null || b == null) return false; 538 | if (a.length != b.length) return false; 539 | 540 | // If you don't care about the order of the elements inside 541 | // the array, you should sort both arrays here. 542 | // Please note that calling sort on an array will modify that array. 543 | // you might want to clone your array first. 544 | 545 | for (var i = 0; i < a.length; ++i) { 546 | if (a[i] !== b[i]) return false; 547 | } 548 | return true; 549 | } 550 | 551 | return { 552 | 553 | create: function (context) { 554 | WriteLog('init v0.12.1'); 555 | 556 | ctx = VSS.getWebContext(); 557 | 558 | getWorkItemFormService().then(function (service) { 559 | service.hasActiveWorkItem() 560 | .then(function success(response) { 561 | if (response == true) { 562 | //form is open 563 | AddTasksOnForm(service); 564 | } 565 | else { 566 | // on grid 567 | if (context.workItemIds && context.workItemIds.length > 0) { 568 | 569 | context.workItemIds.forEach(function (workItemId) { 570 | AddTasksOnGrid(workItemId); 571 | }); 572 | } 573 | else if (context.id) { 574 | var workItemId = context.id; 575 | AddTasksOnGrid(workItemId); 576 | } 577 | } 578 | }); 579 | }) 580 | }, 581 | } 582 | }); 583 | --------------------------------------------------------------------------------