├── 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 | 
33 |
34 | ### Create / open a work item ###
35 |
36 | Find 1-Click Child-Links on toolbar menu
37 |
38 | 
39 |
40 | ### Done ###
41 |
42 | You should now have children associated with the open work item.
43 |
44 | 
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 |
33 |
34 | ### Create / open a work item ###
35 |
36 | Find 1-Click Child-Links on toolbar menu
37 |
38 |
39 |
40 | ### Done ###
41 |
42 | You should now have children associated with the open work item.
43 |
44 |
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 |
--------------------------------------------------------------------------------