├── .gitignore ├── README.md └── src ├── .vs └── config │ └── applicationhost.config ├── Vso.StateModelVisualization.sln └── Vso.StateModelVisualization ├── App.html ├── License.txt ├── PrintGraph.html ├── Properties └── AssemblyInfo.cs ├── StateDiagramDialog.html ├── StateDiagramWitButton.html ├── ThirdPartyNotice.txt ├── Vso.StateModelVisualization.csproj ├── Web.Debug.config ├── Web.Release.config ├── Web.config ├── css └── app.css ├── images ├── Export.png ├── FitTo.png ├── Refresh.png ├── Screen1-small.png ├── Screen2-small.png ├── StateModelShortIcon-16x16.png ├── StateModelShortIcon.png ├── StateModelWideIcon.png ├── WVizDemo.png ├── ZoomZoomIn.png └── ZoomZoomOut.png ├── overview.md ├── package.json ├── scripts ├── app │ ├── AppInsightPageViewsTelemetry.js │ ├── MainMenu.js │ ├── PrintGraph.js │ ├── StateModelGraph.js │ ├── StateModelVisualization.js │ └── TelemetryClient.js ├── cytoscape │ ├── LGPL-LICENSE.txt │ ├── cytoscape-dagre.js │ ├── cytoscape.js │ ├── cytoscape.min.js │ └── dagre.js └── lib │ ├── VSS.SDK.js │ ├── VSS.SDK.min.js │ ├── ai.0.22.9-build00167.js │ └── ai.0.22.9-build00167.min.js ├── vss-extension.internal.json └── vss-extension.json /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | # DNX 42 | project.lock.json 43 | artifacts/ 44 | 45 | *_i.c 46 | *_p.c 47 | *_i.h 48 | *.ilk 49 | *.meta 50 | *.obj 51 | *.pch 52 | *.pdb 53 | *.pgc 54 | *.pgd 55 | *.rsp 56 | *.sbr 57 | *.tlb 58 | *.tli 59 | *.tlh 60 | *.tmp 61 | *.tmp_proj 62 | *.log 63 | *.vspscc 64 | *.vssscc 65 | .builds 66 | *.pidb 67 | *.svclog 68 | *.scc 69 | 70 | # Chutzpah Test files 71 | _Chutzpah* 72 | 73 | # Visual C++ cache files 74 | ipch/ 75 | *.aps 76 | *.ncb 77 | *.opensdf 78 | *.sdf 79 | *.cachefile 80 | 81 | # Visual Studio profiler 82 | *.psess 83 | *.vsp 84 | *.vspx 85 | 86 | # TFS 2012 Local Workspace 87 | $tf/ 88 | 89 | # Guidance Automation Toolkit 90 | *.gpState 91 | 92 | # ReSharper is a .NET coding add-in 93 | _ReSharper*/ 94 | *.[Rr]e[Ss]harper 95 | *.DotSettings.user 96 | 97 | # JustCode is a .NET coding add-in 98 | .JustCode 99 | 100 | # TeamCity is a build add-in 101 | _TeamCity* 102 | 103 | # DotCover is a Code Coverage Tool 104 | *.dotCover 105 | 106 | # NCrunch 107 | _NCrunch_* 108 | .*crunch*.local.xml 109 | 110 | # MightyMoose 111 | *.mm.* 112 | AutoTest.Net/ 113 | 114 | # Web workbench (sass) 115 | .sass-cache/ 116 | 117 | # Installshield output folder 118 | [Ee]xpress/ 119 | 120 | # DocProject is a documentation generator add-in 121 | DocProject/buildhelp/ 122 | DocProject/Help/*.HxT 123 | DocProject/Help/*.HxC 124 | DocProject/Help/*.hhc 125 | DocProject/Help/*.hhk 126 | DocProject/Help/*.hhp 127 | DocProject/Help/Html2 128 | DocProject/Help/html 129 | 130 | # Click-Once directory 131 | publish/ 132 | 133 | # Publish Web Output 134 | *.[Pp]ublish.xml 135 | *.azurePubxml 136 | ## TODO: Comment the next line if you want to checkin your 137 | ## web deploy settings but do note that will include unencrypted 138 | ## passwords 139 | #*.pubxml 140 | 141 | *.publishproj 142 | 143 | # NuGet Packages 144 | *.nupkg 145 | # The packages folder can be ignored because of Package Restore 146 | **/packages/* 147 | # except build/, which is used as an MSBuild target. 148 | !**/packages/build/ 149 | # Uncomment if necessary however generally it will be regenerated when needed 150 | #!**/packages/repositories.config 151 | 152 | # Windows Azure Build Output 153 | csx/ 154 | *.build.csdef 155 | 156 | # Windows Store app package directory 157 | AppPackages/ 158 | 159 | # Visual Studio cache files 160 | # files ending in .cache can be ignored 161 | *.[Cc]ache 162 | # but keep track of directories ending in .cache 163 | !*.[Cc]ache/ 164 | 165 | # Others 166 | ClientBin/ 167 | [Ss]tyle[Cc]op.* 168 | ~$* 169 | *~ 170 | *.dbmdl 171 | *.dbproj.schemaview 172 | *.pfx 173 | *.publishsettings 174 | node_modules/ 175 | orleans.codegen.cs 176 | 177 | # RIA/Silverlight projects 178 | Generated_Code/ 179 | 180 | # Backup & report files from converting an old project file 181 | # to a newer Visual Studio version. Backup files are not needed, 182 | # because we have git ;-) 183 | _UpgradeReport_Files/ 184 | Backup*/ 185 | UpgradeLog*.XML 186 | UpgradeLog*.htm 187 | 188 | # SQL Server files 189 | *.mdf 190 | *.ldf 191 | 192 | # Business Intelligence projects 193 | *.rdl.data 194 | *.bim.layout 195 | *.bim_*.settings 196 | 197 | # Microsoft Fakes 198 | FakesAssemblies/ 199 | 200 | # Node.js Tools for Visual Studio 201 | .ntvs_analysis.dat 202 | 203 | # Visual Studio 6 build log 204 | *.plg 205 | 206 | # Visual Studio 6 workspace options file 207 | *.opt 208 | 209 | # LightSwitch generated files 210 | GeneratedArtifacts/ 211 | _Pvt_Extensions/ 212 | ModelManifest.xml 213 | *.vsix 214 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Work Item State Model Visualization Extension for Visual Studio Team Services 2 | 3 | ## The Gist 4 | This VSTS extension is intended to offer users the ability to visualize the work item state model incl its transitions. 5 | ## Features 6 | - Visualize the work item state model with transitions 7 | - Export the visualization to image 8 | - Zoom in, Zoom out, Zoom to Fit, panning 9 | 10 | ## Roadmap 11 | - Show transition reasons, when API allows 12 | - Allow to jump to visualization from work item form or show it on work item form 13 | 14 | ## Extensibility API Features Used 15 | - MessageArea Control 16 | - TreeView Control 17 | - Work Item Tracking HttpClient 18 | - Core HttpClient 19 | - Toolbars and menus 20 | -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.22310.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vso.StateModelVisualization", "Vso.StateModelVisualization\Vso.StateModelVisualization.csproj", "{63EE87F2-52C8-4BA1-8DF5-8845337EE750}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {63EE87F2-52C8-4BA1-8DF5-8845337EE750}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {63EE87F2-52C8-4BA1-8DF5-8845337EE750}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {63EE87F2-52C8-4BA1-8DF5-8845337EE750}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {63EE87F2-52C8-4BA1-8DF5-8845337EE750}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/App.html: -------------------------------------------------------------------------------- 1 |  16 | 17 | 18 | 19 | 20 | State Diagram in State Visualizer 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
State Model Visualization
40 |
41 | 42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/License.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melborp/StateModelVisualization/2165dd00690464aba86e7733f0b53c802d397c4a/src/Vso.StateModelVisualization/License.txt -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/PrintGraph.html: -------------------------------------------------------------------------------- 1 |  16 | 17 | 18 | 19 | 20 | Export State Diagram Visualization 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
30 |
31 |
32 |
33 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("WebApplication1")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("WebApplication1")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("63ee87f2-52c8-4ba1-8df5-8845337ee750")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/StateDiagramDialog.html: -------------------------------------------------------------------------------- 1 |  16 | 17 | 18 | 19 | 20 | State Diagram in Modal 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
30 | 31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/StateDiagramWitButton.html: -------------------------------------------------------------------------------- 1 |  16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/ThirdPartyNotice.txt: -------------------------------------------------------------------------------- 1 | THIRD-PARTY SOFTWARE NOTICES AND INFORMATION 2 | Do Not Translate or Localize 3 | 4 | This file is based on or incorporates material from the projects listed below (Third Party IP). 5 | The original copyright notice and the license under which Taavi Koosaar received such Third Party IP, 6 | are set forth below. Such licenses and notices are provided for informational purposes only. 7 | Taavi Koosaar licenses the Third Party IP to you under the licensing terms for the State Model Visualization Extension product. 8 | Taavi Koosaar reserves all other rights not expressly granted under this agreement, whether by implication, estoppel or otherwise. 9 | 10 | Provided for Informational Purposes Only 11 | 12 | ---------------------- 13 | 14 | Dagre (https://github.com/cpettitt/dagre/) 15 | 16 | Copyright (c) 2012-2014 Chris Pettitt 17 | 18 | Permission is hereby granted, free of charge, to any person obtaining a copy 19 | of this software and associated documentation files (the "Software"), to deal 20 | in the Software without restriction, including without limitation the rights 21 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 22 | copies of the Software, and to permit persons to whom the Software is 23 | furnished to do so, subject to the following conditions: 24 | 25 | The above copyright notice and this permission notice shall be included in 26 | all copies or substantial portions of the Software. 27 | 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 31 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 33 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 34 | THE SOFTWARE. 35 | 36 | ---------------------- 37 | 38 | Cytoscape.js (https://github.com/cytoscape/cytoscape.js) 39 | 40 | GNU LESSER GENERAL PUBLIC LICENSE 41 | Version 3, 29 June 2007 42 | 43 | Copyright (C) 2007 Free Software Foundation, Inc. 44 | Everyone is permitted to copy and distribute verbatim copies 45 | of this license document, but changing it is not allowed. 46 | 47 | 48 | This version of the GNU Lesser General Public License incorporates 49 | the terms and conditions of version 3 of the GNU General Public 50 | License, supplemented by the additional permissions listed below. 51 | 52 | 0. Additional Definitions. 53 | 54 | As used herein, "this License" refers to version 3 of the GNU Lesser 55 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 56 | General Public License. 57 | 58 | "The Library" refers to a covered work governed by this License, 59 | other than an Application or a Combined Work as defined below. 60 | 61 | An "Application" is any work that makes use of an interface provided 62 | by the Library, but which is not otherwise based on the Library. 63 | Defining a subclass of a class defined by the Library is deemed a mode 64 | of using an interface provided by the Library. 65 | 66 | A "Combined Work" is a work produced by combining or linking an 67 | Application with the Library. The particular version of the Library 68 | with which the Combined Work was made is also called the "Linked 69 | Version". 70 | 71 | The "Minimal Corresponding Source" for a Combined Work means the 72 | Corresponding Source for the Combined Work, excluding any source code 73 | for portions of the Combined Work that, considered in isolation, are 74 | based on the Application, and not on the Linked Version. 75 | 76 | The "Corresponding Application Code" for a Combined Work means the 77 | object code and/or source code for the Application, including any data 78 | and utility programs needed for reproducing the Combined Work from the 79 | Application, but excluding the System Libraries of the Combined Work. 80 | 81 | 1. Exception to Section 3 of the GNU GPL. 82 | 83 | You may convey a covered work under sections 3 and 4 of this License 84 | without being bound by section 3 of the GNU GPL. 85 | 86 | 2. Conveying Modified Versions. 87 | 88 | If you modify a copy of the Library, and, in your modifications, a 89 | facility refers to a function or data to be supplied by an Application 90 | that uses the facility (other than as an argument passed when the 91 | facility is invoked), then you may convey a copy of the modified 92 | version: 93 | 94 | a) under this License, provided that you make a good faith effort to 95 | ensure that, in the event an Application does not supply the 96 | function or data, the facility still operates, and performs 97 | whatever part of its purpose remains meaningful, or 98 | 99 | b) under the GNU GPL, with none of the additional permissions of 100 | this License applicable to that copy. 101 | 102 | 3. Object Code Incorporating Material from Library Header Files. 103 | 104 | The object code form of an Application may incorporate material from 105 | a header file that is part of the Library. You may convey such object 106 | code under terms of your choice, provided that, if the incorporated 107 | material is not limited to numerical parameters, data structure 108 | layouts and accessors, or small macros, inline functions and templates 109 | (ten or fewer lines in length), you do both of the following: 110 | 111 | a) Give prominent notice with each copy of the object code that the 112 | Library is used in it and that the Library and its use are 113 | covered by this License. 114 | 115 | b) Accompany the object code with a copy of the GNU GPL and this license 116 | document. 117 | 118 | 4. Combined Works. 119 | 120 | You may convey a Combined Work under terms of your choice that, 121 | taken together, effectively do not restrict modification of the 122 | portions of the Library contained in the Combined Work and reverse 123 | engineering for debugging such modifications, if you also do each of 124 | the following: 125 | 126 | a) Give prominent notice with each copy of the Combined Work that 127 | the Library is used in it and that the Library and its use are 128 | covered by this License. 129 | 130 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 131 | document. 132 | 133 | c) For a Combined Work that displays copyright notices during 134 | execution, include the copyright notice for the Library among 135 | these notices, as well as a reference directing the user to the 136 | copies of the GNU GPL and this license document. 137 | 138 | d) Do one of the following: 139 | 140 | 0) Convey the Minimal Corresponding Source under the terms of this 141 | License, and the Corresponding Application Code in a form 142 | suitable for, and under terms that permit, the user to 143 | recombine or relink the Application with a modified version of 144 | the Linked Version to produce a modified Combined Work, in the 145 | manner specified by section 6 of the GNU GPL for conveying 146 | Corresponding Source. 147 | 148 | 1) Use a suitable shared library mechanism for linking with the 149 | Library. A suitable mechanism is one that (a) uses at run time 150 | a copy of the Library already present on the user's computer 151 | system, and (b) will operate properly with a modified version 152 | of the Library that is interface-compatible with the Linked 153 | Version. 154 | 155 | e) Provide Installation Information, but only if you would otherwise 156 | be required to provide such information under section 6 of the 157 | GNU GPL, and only to the extent that such information is 158 | necessary to install and execute a modified version of the 159 | Combined Work produced by recombining or relinking the 160 | Application with a modified version of the Linked Version. (If 161 | you use option 4d0, the Installation Information must accompany 162 | the Minimal Corresponding Source and Corresponding Application 163 | Code. If you use option 4d1, you must provide the Installation 164 | Information in the manner specified by section 6 of the GNU GPL 165 | for conveying Corresponding Source.) 166 | 167 | 5. Combined Libraries. 168 | 169 | You may place library facilities that are a work based on the 170 | Library side by side in a single library together with other library 171 | facilities that are not Applications and are not covered by this 172 | License, and convey such a combined library under terms of your 173 | choice, if you do both of the following: 174 | 175 | a) Accompany the combined library with a copy of the same work based 176 | on the Library, uncombined with any other library facilities, 177 | conveyed under the terms of this License. 178 | 179 | b) Give prominent notice with the combined library that part of it 180 | is a work based on the Library, and explaining where to find the 181 | accompanying uncombined form of the same work. 182 | 183 | 6. Revised Versions of the GNU Lesser General Public License. 184 | 185 | The Free Software Foundation may publish revised and/or new versions 186 | of the GNU Lesser General Public License from time to time. Such new 187 | versions will be similar in spirit to the present version, but may 188 | differ in detail to address new problems or concerns. 189 | 190 | Each version is given a distinguishing version number. If the 191 | Library as you received it specifies that a certain numbered version 192 | of the GNU Lesser General Public License "or any later version" 193 | applies to it, you have the option of following the terms and 194 | conditions either of that published version or of any later version 195 | published by the Free Software Foundation. If the Library as you 196 | received it does not specify a version number of the GNU Lesser 197 | General Public License, you may choose any version of the GNU Lesser 198 | General Public License ever published by the Free Software Foundation. 199 | 200 | If the Library as you received it specifies that a proxy can decide 201 | whether future versions of the GNU Lesser General Public License shall 202 | apply, that proxy's public statement of acceptance of any version is 203 | permanent authorization for you to choose that version for the 204 | Library. 205 | 206 | ---------------------- 207 | 208 | 209 | -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/Vso.StateModelVisualization.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 8 | 9 | 2.0 10 | {63EE87F2-52C8-4BA1-8DF5-8845337EE750} 11 | {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} 12 | Library 13 | Properties 14 | WebApplication1 15 | WebApplication1 16 | v4.5 17 | true 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | true 26 | full 27 | false 28 | bin\ 29 | DEBUG;TRACE 30 | prompt 31 | 4 32 | 33 | 34 | pdbonly 35 | true 36 | bin\ 37 | TRACE 38 | prompt 39 | 4 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | Web.config 87 | 88 | 89 | Web.config 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 10.0 114 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | True 124 | True 125 | 57453 126 | / 127 | http://localhost:57453/ 128 | False 129 | False 130 | 131 | 132 | False 133 | 134 | 135 | 136 | 137 | 144 | -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/Web.Debug.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/Web.Release.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/css/app.css: -------------------------------------------------------------------------------- 1 |  /*--------------------------------------------------------------------- 2 | // 3 | // This code is licensed under the MIT License. 4 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 5 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 6 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 7 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. 8 | // 9 | // 10 | // Part of the State Model Visualization VSTS extension. 11 | // This file defines CSS styling for the application. 12 | // 13 | //---------------------------------------------------------------------*/ 14 | 15 | .icon-zoom-100-smv { 16 | background-image: url("../images/Refresh.png"); 17 | background-repeat: no-repeat !important; 18 | background-size: 16px 16px; 19 | } 20 | 21 | .icon-zoom-out-smv { 22 | background-image: url("../images/ZoomZoomOut.png"); 23 | background-repeat: no-repeat !important; 24 | background-size: 16px 16px; 25 | } 26 | 27 | .icon-zoom-in-smv { 28 | background-image: url("../images/ZoomZoomIn.png"); 29 | background-repeat: no-repeat !important; 30 | background-size: 16px 16px; 31 | } 32 | 33 | .icon-fit-to-smv { 34 | background-image: url("../images/FitTo.png"); 35 | background-repeat: no-repeat !important; 36 | background-size: 16px 16px; 37 | } 38 | 39 | .icon-export-smv { 40 | background-image: url("../images/Export.png"); 41 | background-repeat: no-repeat !important; 42 | background-size: 16px 16px; 43 | } 44 | 45 | #cy { 46 | height: 95%; 47 | width: 100%; 48 | position: absolute; 49 | left: 0; 50 | /*top: 100px;*/ 51 | } 52 | -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/images/Export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melborp/StateModelVisualization/2165dd00690464aba86e7733f0b53c802d397c4a/src/Vso.StateModelVisualization/images/Export.png -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/images/FitTo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melborp/StateModelVisualization/2165dd00690464aba86e7733f0b53c802d397c4a/src/Vso.StateModelVisualization/images/FitTo.png -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/images/Refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melborp/StateModelVisualization/2165dd00690464aba86e7733f0b53c802d397c4a/src/Vso.StateModelVisualization/images/Refresh.png -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/images/Screen1-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melborp/StateModelVisualization/2165dd00690464aba86e7733f0b53c802d397c4a/src/Vso.StateModelVisualization/images/Screen1-small.png -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/images/Screen2-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melborp/StateModelVisualization/2165dd00690464aba86e7733f0b53c802d397c4a/src/Vso.StateModelVisualization/images/Screen2-small.png -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/images/StateModelShortIcon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melborp/StateModelVisualization/2165dd00690464aba86e7733f0b53c802d397c4a/src/Vso.StateModelVisualization/images/StateModelShortIcon-16x16.png -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/images/StateModelShortIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melborp/StateModelVisualization/2165dd00690464aba86e7733f0b53c802d397c4a/src/Vso.StateModelVisualization/images/StateModelShortIcon.png -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/images/StateModelWideIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melborp/StateModelVisualization/2165dd00690464aba86e7733f0b53c802d397c4a/src/Vso.StateModelVisualization/images/StateModelWideIcon.png -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/images/WVizDemo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melborp/StateModelVisualization/2165dd00690464aba86e7733f0b53c802d397c4a/src/Vso.StateModelVisualization/images/WVizDemo.png -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/images/ZoomZoomIn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melborp/StateModelVisualization/2165dd00690464aba86e7733f0b53c802d397c4a/src/Vso.StateModelVisualization/images/ZoomZoomIn.png -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/images/ZoomZoomOut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melborp/StateModelVisualization/2165dd00690464aba86e7733f0b53c802d397c4a/src/Vso.StateModelVisualization/images/ZoomZoomOut.png -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/overview.md: -------------------------------------------------------------------------------- 1 | ## Visualize work item type states and transitions## 2 | 3 | [![Demo](images/wvizdemo.png)](https://channel9.msdn.com/Series/Visual-Studio-ALM-Rangers-Demos/VS-Team-Services-State-Model-Visualization-Extension) Every work item type in Visual Studio Team Services has a states, transitions and reasons defined. With this extension you can visualize those states and transitions for regular and hidden work item types. 4 | 5 | ![Visualize](images/Screen1-small.png) 6 | 7 | ### Export for offline viewing ### 8 | 9 | Export your chart visualization for offline viewing or printing. 10 | 11 | ![Export](images/Screen2-small.png) 12 | 13 | ## Quick steps to get started ## 14 | 15 | - **Visualize** 16 | 1. Navigate to your team project. 17 | 1. Select **WORK** hub group. 18 | 1. Navigate to a **State Visualizer** hub. 19 | 1. Use the left tree view to select work item type and the states and transitions are visualized on the right side. 20 | 1. Click on `Zoom In`, `Zoom Out`, `Zoom to original size` or `Fit To icons` on the toolbar to re-size. 21 | - **State Diagram from Work Item** 22 | 1. Open any work item 23 | - If you are using the classic WI item form, select `State Diagram` on the toolbar. 24 | - Otherwise click on `...` and select `State Diagram`. 25 | - State Diagram Visualization dialog will open up for the selected work item type. 26 | - **Export** 27 | 1. Export the visualization in any browser. 28 | 29 | 30 | ## Planned features ## 31 | 32 | - Showing reasons for transitions. 33 | 34 | 35 | ## Feedback ## 36 | 37 | If you like this extension, please leave a review and feedback. If you'd have suggestions or an issue, please [file an issue to give me a chance to fix it](https://github.com/melborp/StateModelVisualization/issues). 38 | 39 | ## Release History ## 40 | 41 | ### v1.3 ### 42 | 43 | - **Bug fixes** 44 | 45 | 1. Fixed issue with exporting visualization on TFS on-premis (#12) 46 | 1. Fixed issue where Fit To Screen didnt quite fit (#11) 47 | 1. Moved extension to Plan and Track category, added application insight, upgraded VSS SDK (#14, #10, #8) 48 | 1. Fixed script paths to be same everywhere (case sensitive in CDN) (#7) 49 | 50 | ## Learn more ## 51 | 52 | The source to this extension is available on GitHub: [StateModelVisualization](https://github.com/melborp/StateModelVisualization). 53 | 54 | To learn more about developing an extension for Visual Studio Team Services, see the [overview of extensions](https://www.visualstudio.com/en-us/integrate/extensions/overview). 55 | 56 | [Third Party Notice](https://marketplace.visualstudio.com/_apis/public/gallery/publisher/taavi-koosaar/extension/StateModelVisualization/latest/assetbyname/ThirdPartyNotice.txt). 57 | -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "name": "VstsStateModelVisualization", 4 | "private": true, 5 | "devDependencies": { 6 | "vss-web-extension-sdk": "~1.102.0" 7 | } 8 | } -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/scripts/app/AppInsightPageViewsTelemetry.js: -------------------------------------------------------------------------------- 1 | var appInsights=window.appInsights||function(config){ function r(config){t[config]=function(){var i=arguments;t.queue.push(function(){t[config].apply(t,i)})}}var t={config:config},u=document,e=window,o="script",s=u.createElement(o),i,f;s.src=config.url||"//az416426.vo.msecnd.net/scripts/a/ai.0.js";u.getElementsByTagName(o)[0].parentNode.appendChild(s);try{t.cookie=u.cookie}catch(h){}for(t.queue=[],i=["Event","Exception","Metric","PageView","Trace","Dependency"];i.length;)r("track"+i.pop());return r("setAuthenticatedUserContext"),r("clearAuthenticatedUserContext"),config.disableExceptionTracking||(i="onerror",r("_"+i),f=e[i],e[i]=function(config,r,u,e,o){var s=f&&f(config,r,u,e,o);return s!==!0&&t["_"+i](config,r,u,e,o),s}),t }({ instrumentationKey:"43786d2b-25eb-4eaf-96c7-ec7f5ccba5a0" }); window.appInsights=appInsights; appInsights.trackPageView(); -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/scripts/app/MainMenu.js: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------- 2 | // 3 | // This code is licensed under the MIT License. 4 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 5 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 6 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 7 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. 8 | // 9 | // 10 | // Part of the State Model Visualization VSTS extension. 11 | // This class defines the MainMenu creation in the VSTS Hub. 12 | // 13 | //---------------------------------------------------------------------*/ 14 | 15 | var __extends = (this && this.__extends) || function (d, b) { 16 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 17 | function __() { this.constructor = d; } 18 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 19 | }; 20 | 21 | define(["require", "exports", "VSS/Utils/Core", 22 | "VSS/Controls", "VSS/Controls/Menus", "scripts/app/StateModelGraph", "scripts/app/TelemetryClient"], 23 | function (require, exports, Core, Controls, MenuControls, StateModelGraph, TelemetryClient) { 24 | 25 | var ItemsView = (function (_super) { 26 | __extends(ItemsView, _super); 27 | 28 | function ItemsView(options) { 29 | _super.call(this, options); 30 | this._menu = null; 31 | this._graph = StateModelGraph.graph; 32 | } 33 | 34 | 35 | /* 36 | * Initialize will be called when this control is created. This will setup the UI, 37 | * attach to events, etc. 38 | */ 39 | ItemsView.prototype.initialize = function () { 40 | _super.prototype.initialize.call(this); 41 | 42 | this._createToolbar(); 43 | }; 44 | 45 | ItemsView.prototype._createToolbar = function () { 46 | this._menu = Controls.BaseControl.createIn(MenuControls.MenuBar, this._element.find(".hub-pivot-toolbar"), { 47 | items: this._createToolbarItems() 48 | }); 49 | MenuControls.menuManager.attachExecuteCommand(Core.delegate(this, this._onToolbarItemClick)); 50 | }; 51 | 52 | /* 53 | * Create the actual toolbar items 54 | */ 55 | ItemsView.prototype._createToolbarItems = function () { 56 | var items = []; 57 | 58 | items.push({ id: "zoom-in", text: "Zoom In", title: "Zoom In", showText: false, icon: "icon-zoom-in-smv", disabled: true }); 59 | items.push({ id: "zoom-out", text: "Zoom Out", title: "Zoom Out", showText: false, icon: "icon-zoom-out-smv", disabled: true }); 60 | items.push({ id: "zoom-100", text: "Zoom 100%", title: "Zoom to 100%", showText: false, icon: "icon-zoom-100-smv", disabled: true }); 61 | items.push({ id: "fit-to", text: "Fit to screen", title: "Fit to screen", showText: false, icon: "icon-fit-to-smv", disabled: true }); 62 | 63 | items.push({ separator: true }); 64 | 65 | items.push({ id: "export-graph", text: "Export Graph", title: "Export Graph", showText: false, icon: "icon-export-smv", disabled: true }); 66 | 67 | return items; 68 | }; 69 | 70 | /* 71 | * Fit the graph to the current window size 72 | */ 73 | ItemsView.prototype._fitTo = function () { 74 | TelemetryClient.getClient().trackEvent("Menu.FitTo"); 75 | if (!$("#fit-to").hasClass("disabled")) { 76 | this._graph.fitTo(); 77 | } 78 | }; 79 | 80 | /* 81 | * Zoom the diagram in one unit 82 | */ 83 | ItemsView.prototype._zoomIn = function () { 84 | TelemetryClient.getClient().trackEvent("Menu.ZoomIn"); 85 | if (!$("#zoom-in").hasClass("disabled")) { 86 | this._graph.zoomIn(); 87 | } 88 | }; 89 | 90 | /* 91 | * Zoom the diagram out one unit 92 | */ 93 | ItemsView.prototype._zoomOut = function () { 94 | TelemetryClient.getClient().trackEvent("Menu.ZoomOut"); 95 | if (!$("#zoom-out").hasClass("disabled")) { 96 | this._graph.zoomOut(); 97 | } 98 | }; 99 | 100 | /* 101 | * Set the diagram to 100% 102 | */ 103 | ItemsView.prototype._zoom100 = function () { 104 | TelemetryClient.getClient().trackEvent("Menu.ZoomTo100"); 105 | if (!$("#zoom-100").hasClass("disabled")) { 106 | this._graph.zoomTo100(); 107 | } 108 | }; 109 | 110 | /* 111 | * Handle a button click on the toolbar 112 | */ 113 | ItemsView.prototype._onToolbarItemClick = function (sender, args) { 114 | var command = args.get_commandName(), commandArgument = args.get_commandArgument(), that = this, result = false; 115 | switch (command) { 116 | case "zoom-in": 117 | this._zoomIn(); 118 | break; 119 | case "zoom-out": 120 | this._zoomOut(); 121 | break; 122 | case "zoom-100": 123 | this._zoom100(); 124 | break; 125 | case "fit-to": 126 | this._fitTo(); 127 | break; 128 | case "export-graph": 129 | this._exportGraph(); 130 | break; 131 | default: 132 | result = true; 133 | break; 134 | } 135 | return result; 136 | }; 137 | 138 | ItemsView.prototype._exportGraph = function () { 139 | TelemetryClient.getClient().trackEvent("Menu.ExportGraph"); 140 | var png = this._graph.exportImage(); 141 | var self = this; 142 | 143 | VSS.getService(VSS.ServiceIds.Dialog).then(function (dlg) { 144 | var printGraphDialog; 145 | 146 | //TODO: later make dialog same size as window and offer full screen option 147 | var opts = { 148 | width: window.screen.width, 149 | height: window.screen.height, 150 | title: "Export State Diagram Visualization", 151 | buttons: null 152 | }; 153 | 154 | dlg.openDialog(VSS.getExtensionContext().publisherId + "." + VSS.getExtensionContext().extensionId + ".state-diagram-visualization-print-graph-dialog", opts).then(function (dialog) { 155 | dialog.getContributionInstance("state-diagram-visualization-print-graph-dialog").then(function (ci) { 156 | printGraphDialog = ci; 157 | printGraphDialog.start(png, self._graph.currentWitType); 158 | }, function (err) { 159 | alert(err.message); 160 | }); 161 | }); 162 | }); 163 | }; 164 | 165 | /* 166 | * Enables the toolbar menu items - to be called after the work item diagram is set initially 167 | */ 168 | ItemsView.prototype.EnableToolbar = function() { 169 | this._menu.updateCommandStates([ 170 | { id: "zoom-in", disabled: false }, 171 | { id: "zoom-out", disabled: false }, 172 | { id: "zoom-100", disabled: false }, 173 | { id: "fit-to", disabled: false }, 174 | { id: "export-graph", disabled: false } 175 | ]); 176 | } 177 | 178 | 179 | return ItemsView; 180 | })(Controls.BaseControl); 181 | exports.ItemsView = ItemsView; 182 | 183 | Controls.Enhancement.registerEnhancement(ItemsView, ".hub-view"); 184 | }); -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/scripts/app/PrintGraph.js: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------- 2 | // 3 | // This code is licensed under the MIT License. 4 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 5 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 6 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 7 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. 8 | // 9 | // 10 | // Part of the State Model Visualization VSO extension by the 11 | // ALM Rangers. The application logic for finding work item by id dialog view. 12 | // 13 | //---------------------------------------------------------------------*/ 14 | 15 | var __extends = (this && this.__extends) || function (d, b) { 16 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 17 | function __() { this.constructor = d; } 18 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 19 | }; 20 | define( 21 | ["require", "exports", "VSS/Controls/Dialogs"], 22 | function (require, exports, Dialogs) { 23 | var PrintGraph = (function (_super) { 24 | __extends(PrintGraph, _super); 25 | function PrintGraph(context) { 26 | _super.call(this); 27 | var self = this; 28 | self.context = context; 29 | } 30 | PrintGraph.prototype.start = function (img, witType) { 31 | var self = this; 32 | 33 | var d = new Date(); 34 | 35 | $("#printTitle").text("Visualization of " + witType); 36 | $("#printDateTime").text("Generated " + d.toLocaleDateString()); 37 | $("#graphImage").attr("src",img); 38 | }; 39 | 40 | return PrintGraph; 41 | })(Dialogs.ModalDialog); 42 | exports.PrintGraph = PrintGraph; 43 | }); -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/scripts/app/StateModelGraph.js: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------- 2 | // 3 | // This code is licensed under the MIT License. 4 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 5 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 6 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 7 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. 8 | // 9 | // 10 | // Part of the State Model Visualization VSTS extension. 11 | // This file defines the State Model Graph logic tied to Cytoscape.js. 12 | // 13 | //---------------------------------------------------------------------*/ 14 | 15 | define(["require", "exports", "scripts/cytoscape/cytoscape", "scripts/cytoscape/dagre", "scripts/cytoscape/cytoscape-dagre", "scripts/app/TelemetryClient"], function (require, exports, cytoscape, dagre, cydagre, TelemetryClient) { 16 | var StateModelGraph = (function() { 17 | 18 | var zoomStepSize = 0.1; 19 | var zoom100 = 1; 20 | 21 | function StateModelGraph(container, cytoscape) { 22 | this.container = container; 23 | this.cy = null; 24 | this.currentWitType = ""; 25 | this.cytoscape = cytoscape; 26 | cydagre(cytoscape, dagre); 27 | } 28 | 29 | StateModelGraph.prototype.create = function(witTypeName, callback, callbackData) { 30 | var self = this; 31 | self.currentWitType = witTypeName; 32 | self.cytoscape({ 33 | container : self.container[0], 34 | style: self.cytoscape.stylesheet() 35 | .selector('node') 36 | .css({ 37 | 'content': 'data(name)', 38 | 'text-valign': 'center', 39 | 'text-wrap':'wrap', 40 | 'color': 'black', 41 | 'width': '75px', 42 | 'height': '75px', 43 | 'background-color': 'rgba(254,199,0,1)', 44 | 'border-color': 'black', 45 | 'border-width': '0.25px', 46 | 'font-size': '12px', 47 | 'font-family': 'Segoe UI,Tahoma,Arial,Verdana' 48 | }) 49 | .selector('node#Initial') 50 | .css({ 51 | 'width': '8px', 52 | 'height': '8px', 53 | 'background-color': '#000000', 54 | 'border-color': 'black', 55 | 'border-width': '0.25px', 56 | }) 57 | .selector('edge') 58 | .css({ 59 | 'target-arrow-shape': 'triangle' 60 | }), 61 | 62 | layout: { 63 | name: 'dagre', 64 | fit: false, 65 | rankDir: 'TB' 66 | }, 67 | 68 | // on graph initial layout done (could be async depending on layout...) 69 | ready: function() { 70 | window.cy = this; 71 | self.cy = this; 72 | 73 | // giddy up... 74 | cy.zoom(zoom100); 75 | cy.center(); 76 | 77 | cy.minZoom(zoomStepSize); 78 | cy.maxZoom(10); 79 | cy.elements().unselectify(); 80 | cy.userZoomingEnabled(false); 81 | 82 | callback(callbackData); 83 | } 84 | }); 85 | } 86 | 87 | StateModelGraph.prototype.fitTo = function () { 88 | this.cy.fit(); 89 | } 90 | 91 | StateModelGraph.prototype.zoomIn = function () { 92 | var currentZoom = this.cy.zoom(); 93 | this.cy.zoom(currentZoom + zoomStepSize); 94 | //this.cy.center(); 95 | } 96 | 97 | StateModelGraph.prototype.zoomOut = function () { 98 | var currentZoom = this.cy.zoom(); 99 | this.cy.zoom(currentZoom - zoomStepSize); 100 | //this.cy.center(); 101 | } 102 | 103 | StateModelGraph.prototype.zoomTo100 = function () { 104 | this.cy.zoom(zoom100); 105 | //this.cy.center(); 106 | } 107 | StateModelGraph.prototype.exportImage = function () { 108 | var self = this; 109 | var png64 = self.cy.png({ full: true, scale : 1}); 110 | return png64; 111 | } 112 | StateModelGraph.prototype.resize = function () { 113 | var self = this; 114 | if (self.cy == null) 115 | return; 116 | self.cy.resize(); 117 | } 118 | 119 | StateModelGraph.prototype.getTestGrahpData = function() { 120 | var data = { 121 | nodes: [ 122 | { data: { id: 'Initial', name: '' }, position: { x: 150, y: 0 } }, 123 | { data: { id: 'a', name: 'To Do' }, position: { x: 150, y: 50 } }, 124 | { data: { id: 'b', name: 'In Progress' }, position: { x: 75, y: 125 } }, 125 | { data: { id: 'c', name: 'Done' }, position: { x: 150, y: 125 } }, 126 | { data: { id: 'd', name: 'Removed' }, position: { x: 225, y: 125 } } 127 | ], 128 | edges: [ 129 | { data: { source: 'Initial', target: 'a' } }, 130 | { data: { source: 'a', target: 'b' } }, 131 | { data: { source: 'a', target: 'c' } }, 132 | { data: { source: 'a', target: 'd' } }, 133 | { data: { source: 'b', target: 'a' } }, 134 | { data: { source: 'b', target: 'c' } }, 135 | { data: { source: 'b', target: 'd' } }, 136 | { data: { source: 'c', target: 'a' } }, 137 | { data: { source: 'c', target: 'b' } }, 138 | { data: { source: 'd', target: 'a' } } 139 | ] 140 | }; 141 | return data; 142 | } 143 | 144 | StateModelGraph.prototype.prepareVisualizationData = function(selectedWitName, allWits) 145 | { 146 | var selectedWit; 147 | //Find the selected wit 148 | for (var i = 0; i < allWits.length; i++) { 149 | if (allWits[i].name === selectedWitName) { 150 | selectedWit = allWits[i]; 151 | } 152 | } 153 | 154 | if (selectedWit == undefined) 155 | return null; 156 | 157 | //Create nodes and edges for graph 158 | var states = new Array(); 159 | 160 | var rank1Elements = new Array(); 161 | var rank2Elements = new Array(); 162 | var rank3Elements = new Array(); 163 | 164 | var rankedArray = new Array(); 165 | 166 | for (state in selectedWit.transitions) { 167 | states.push(state); 168 | } 169 | 170 | var initialNodeId = 'Initial'; 171 | var initialNodeTargetId; 172 | var nodes = new Array(); 173 | for (state in selectedWit.transitions) { 174 | var newNode; 175 | if (state === "") { 176 | newNode = { group: 'nodes', data: { id: initialNodeId, name: '' } }; 177 | } else { 178 | newNode = { group: 'nodes', data: { id: state, name: state } }; 179 | } 180 | 181 | nodes.push(newNode); 182 | 183 | var transitions = selectedWit.transitions[state]; 184 | 185 | for (var j = 0; j < transitions.length; j++) { 186 | if (newNode.data.id === transitions[j].to) 187 | continue; 188 | //Check if the TO state is the State array, sometimes there's TO transitions into states that dont exist. not sure why. 189 | if ($.inArray(transitions[j].to, states) === -1) 190 | continue; 191 | 192 | var edge = { group: 'edges', data: { id: newNode.data.id + "-" + transitions[j].to, source: newNode.data.id, target: transitions[j].to } }; 193 | 194 | if (newNode.data.id === initialNodeId) { 195 | initialNodeTargetId = transitions[j].to; 196 | rank2Elements.push(edge);//the edge connected to initial is ranked 2 197 | } else { 198 | rank3Elements.push(edge);//the other edges are ranked 3 199 | } 200 | } 201 | } 202 | 203 | //rank nodes 204 | nodes.forEach(function(node) { 205 | if (node.data.id === "Initial") { 206 | rank1Elements.push(node); 207 | } 208 | else if (initialNodeTargetId === node.data.id) {//the node connected to initial is ranked 2 209 | rank2Elements.push(node); 210 | } else { 211 | rank3Elements.push(node); //the other nodes are ranked 3 212 | } 213 | }); 214 | 215 | rankedArray.push(rank1Elements); 216 | rankedArray.push(rank2Elements); 217 | rankedArray.push(rank3Elements); 218 | 219 | return rankedArray; 220 | } 221 | 222 | StateModelGraph.prototype.addElements = function (elements) { 223 | var self = this; 224 | var newElements = self.cy.collection(); 225 | 226 | if (elements.length > 0) { 227 | newElements = self.cy.add(elements); 228 | self.refreshLayout(); 229 | } 230 | return newElements; 231 | } 232 | 233 | StateModelGraph.prototype.refreshLayout = function () { 234 | var self = this; 235 | self.cy.layout( 236 | { 237 | name: 'dagre', 238 | rankDir: 'TB', 239 | //padding: 50, 240 | fit: true, 241 | minLen: 2, 242 | stop: function () { self.zoomTo100(); } 243 | }); 244 | } 245 | 246 | return StateModelGraph; 247 | })(); 248 | exports.graph = new StateModelGraph($("#cy"), cytoscape); 249 | }); -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/scripts/app/StateModelVisualization.js: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------- 2 | // 3 | // This code is licensed under the MIT License. 4 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 5 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 6 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 7 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. 8 | // 9 | // 10 | // Part of the State Model Visualization VSTS extension. 11 | // The main application flow and logic. 12 | // 13 | //---------------------------------------------------------------------*/ 14 | 15 | define(["require", "exports", "VSS/Controls", "VSS/Controls/TreeView", "VSS/Controls/Menus", "VSS/Service", "TFS/WorkItemTracking/RestClient", 16 | "scripts/app/MainMenu", "scripts/app/StateModelGraph", "scripts/app/TelemetryClient"], 17 | function (require, exports, Controls, TreeView, Menus, VssService, TfsWitRest, MainMenu, StateModelGraph, TelemetryClient) { 18 | var StateModelVisualization = (function() { 19 | 20 | var treeView; 21 | var allWits; 22 | var allCategories; 23 | var graph; 24 | var mainMenu; 25 | 26 | function StateModelVisualization() { 27 | } 28 | 29 | StateModelVisualization.prototype.start = function() { 30 | var self = this; 31 | var context = VSS.getWebContext(); 32 | 33 | mainMenu = Controls.Enhancement.enhance(MainMenu.ItemsView, $(".hub-view"), {}); 34 | graph = StateModelGraph.graph; 35 | 36 | // Get a WIT client to make REST calls to VSTS 37 | var witClient = VssService.getCollectionClient(TfsWitRest.WorkItemTrackingHttpClient); 38 | 39 | var configuration = VSS.getConfiguration().action; 40 | 41 | var afterGraphReady = function (callbackData) { 42 | var data = graph.prepareVisualizationData(callbackData.name, allWits); 43 | for (var i = 0; i < data.length; i++) { 44 | graph.addElements(data[i]); 45 | } 46 | 47 | //activate toolbar 48 | mainMenu.EnableToolbar(); 49 | //hookup resize with cytoscape 50 | $(window).on("resize", graph.resize); 51 | } 52 | 53 | if (configuration) { 54 | TelemetryClient.getClient().trackEvent("Visualize.FromWorkItemForm"); 55 | witClient.getWorkItem(configuration.workItemId, ["System.Id", "System.Title", "System.WorkItemType"]).then(function (wit) { 56 | var witTypeName = wit.fields["System.WorkItemType"]; 57 | witClient.getWorkItemType(context.project.name, witTypeName).then(function (witType) { 58 | allWits = new Array(); 59 | allWits.push(witType); 60 | 61 | graph.create(witType.name, afterGraphReady, { name: witType.name }); 62 | }); 63 | }); 64 | } else { 65 | TelemetryClient.getClient().trackEvent("Visualize.FromStateVisualizer"); 66 | witClient.getWorkItemTypes(context.project.name).then(function(wits) { 67 | witClient.getWorkItemTypeCategory(context.project.name).then(function(categories) { 68 | allWits = wits; 69 | allCategories = categories; 70 | 71 | var hiddenCategory = categories.value.filter(function(category) { return category.referenceName === "Microsoft.HiddenCategory"; })[0]; 72 | 73 | var treeContainer = $(".work-item-type-tree-container"); 74 | var firstNode; 75 | 76 | function convertToTreeNodes(items) { 77 | var workItems = new TreeView.TreeNode("Work Items"); 78 | workItems.expanded = true; 79 | var hiddenWorkItems = new TreeView.TreeNode("Hidden Work Items"); 80 | hiddenWorkItems.expanded = true; 81 | 82 | items.forEach(function(item) { 83 | var node = new TreeView.TreeNode(item.name); 84 | //default root 85 | var root = workItems; 86 | 87 | //double loop, not the best, but simplest. 8 items in hidden category. 88 | for (var i = 0; i < hiddenCategory.workItemTypes.length; i++) { 89 | if (hiddenCategory.workItemTypes[i].name === item.name) { 90 | root = hiddenWorkItems; 91 | break; 92 | } 93 | } 94 | 95 | root.add(node); 96 | if (item.name === wits[0].name) { 97 | firstNode = node; 98 | } 99 | }); 100 | 101 | var sortChildren = function(a, b) { 102 | if (a.text > b.text) { 103 | return 1; 104 | } 105 | if (a.text < b.text) { 106 | return -1; 107 | } 108 | // a must be equal to b 109 | return 0; 110 | }; 111 | 112 | workItems.children.sort(sortChildren); 113 | hiddenWorkItems.children.sort(sortChildren); 114 | 115 | var result = new Array(); 116 | result.push(workItems); 117 | result.push(hiddenWorkItems); 118 | return result; 119 | } 120 | 121 | var treeViewOptions = { nodes: convertToTreeNodes(wits) }; 122 | treeView = Controls.create(TreeView.TreeView, treeContainer, treeViewOptions); 123 | 124 | 125 | 126 | // Attach selectionchanged event using a DOM element containing treeview 127 | treeContainer.bind("selectionchanged", function (e, args) { 128 | TelemetryClient.getClient().trackEvent("Visualization.SelectedWorkItemType"); 129 | treeView.TreeNode = args.selectedNode; 130 | var selectedNode = args.selectedNode; 131 | if (selectedNode && selectedNode.text !== "Work Items" && selectedNode.text !== "Hidden Work Items") { 132 | graph.create(selectedNode.text, afterGraphReady, { name: selectedNode.text }); 133 | } 134 | }); 135 | 136 | //show first WIT as default 137 | graph.create(wits[0].name, afterGraphReady, { name: wits[0].name }); 138 | 139 | //select treenode 140 | treeView.TreeNode = firstNode; 141 | }); 142 | }); 143 | } 144 | } 145 | return StateModelVisualization; 146 | })(); 147 | 148 | exports.smv = new StateModelVisualization(); 149 | }); -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/scripts/app/TelemetryClient.js: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | define(["require", "exports", "scripts/lib/ai.0.22.9-build00167"], 5 | function (require, exports, Microsoft2) { 6 | 7 | var telemetryClient; 8 | function getClient(){ 9 | if (!this.telemetryClient) { 10 | this.telemetryClient = new TelemetryClient(); 11 | this.telemetryClient.Init(); 12 | } 13 | 14 | return this.telemetryClient; 15 | } 16 | 17 | var TelemetryClient = (function () { 18 | function TelemetryClient() { 19 | this.appInsightsClient = null;//: Microsoft.ApplicationInsights.AppInsights; 20 | } 21 | 22 | TelemetryClient.prototype.Init= function() { 23 | try { 24 | var snippet = { 25 | config: { 26 | instrumentationKey: "43786d2b-25eb-4eaf-96c7-ec7f5ccba5a0", 27 | } 28 | }; 29 | var x = VSS.getExtensionContext(); 30 | 31 | var init = new Microsoft.ApplicationInsights.Initialization(snippet); 32 | this.appInsightsClient = init.loadAppInsights(); 33 | 34 | var webContext = VSS.getWebContext(); 35 | this.appInsightsClient.setAuthenticatedUserContext(webContext.user.id, webContext.collection.id); 36 | } 37 | catch (e) { 38 | this.appInsightsClient = null; 39 | console.log(e); 40 | } 41 | } 42 | 43 | TelemetryClient.prototype.startTrackPageView= function(name){ //?: string) { 44 | try { 45 | if (this.appInsightsClient != null) { 46 | this.appInsightsClient.startTrackPage(name); 47 | } 48 | } 49 | catch (e) { 50 | console.log(e); 51 | } 52 | } 53 | 54 | TelemetryClient.prototype.stopTrackPageView = function(name){ //?: string) { 55 | try { 56 | if (this.appInsightsClient != null) { 57 | this.appInsightsClient.stopTrackPage(name); 58 | } 59 | } 60 | catch (e) { 61 | console.log(e); 62 | } 63 | } 64 | 65 | TelemetryClient.prototype.trackPageView= function(name, url, properties, measurements, duration) { 66 | try { 67 | if (this.appInsightsClient != null) { 68 | this.appInsightsClient.trackPageView( name, url, properties, measurements, duration); 69 | } 70 | } 71 | catch (e) { 72 | console.log(e); 73 | } 74 | } 75 | 76 | TelemetryClient.prototype.trackEvent = function(name, properties, measurements) { 77 | try { 78 | if (this.appInsightsClient != null) { 79 | this.appInsightsClient.trackEvent( name, properties, measurements); 80 | this.appInsightsClient.flush(); 81 | } 82 | } 83 | catch (e) { 84 | console.log(e); 85 | } 86 | } 87 | 88 | TelemetryClient.prototype.trackException = function(exception, handledAt, properties, measurements) { 89 | try { 90 | if (this.appInsightsClient != null) { 91 | this.appInsightsClient.trackException(exception, handledAt, properties, measurements); 92 | this.appInsightsClient.flush(); 93 | } 94 | } 95 | catch (e) { 96 | console.log(e); 97 | } 98 | } 99 | 100 | TelemetryClient.prototype.trackMetric = function(name, average, sampleCount, min, max, propertiesObject) { 101 | try { 102 | if (this.appInsightsClient != null) { 103 | this.appInsightsClient.trackMetric( name, average, sampleCount, min, max, properties); 104 | this.appInsightsClient.flush(); 105 | } 106 | } 107 | catch (e) { 108 | console.log(e); 109 | } 110 | } 111 | 112 | return TelemetryClient; 113 | })(); 114 | exports.TelemetryClient = TelemetryClient; 115 | exports.getClient = getClient; 116 | }); 117 | -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/scripts/cytoscape/LGPL-LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/scripts/cytoscape/cytoscape-dagre.js: -------------------------------------------------------------------------------- 1 | (function(){ 'use strict'; 2 | 3 | // registers the extension on a cytoscape lib ref 4 | var register = function( cytoscape, dagre ){ 5 | if( !cytoscape || !dagre ){ return; } // can't register if cytoscape unspecified 6 | 7 | var isFunction = function(o){ return typeof o === 'function'; }; 8 | 9 | // default layout options 10 | var defaults = { 11 | // dagre algo options, uses default value on undefined 12 | nodeSep: undefined, // the separation between adjacent nodes in the same rank 13 | edgeSep: undefined, // the separation between adjacent edges in the same rank 14 | rankSep: undefined, // the separation between adjacent nodes in the same rank 15 | rankDir: undefined, // 'TB' for top to bottom flow, 'LR' for left to right 16 | minLen: function( edge ){ return 1; }, // number of ranks to keep between the source and target of the edge 17 | edgeWeight: function( edge ){ return 1; }, // higher weight edges are generally made shorter and straighter than lower weight edges 18 | 19 | // general layout options 20 | fit: true, // whether to fit to viewport 21 | padding: 30, // fit padding 22 | animate: false, // whether to transition the node positions 23 | animationDuration: 500, // duration of animation in ms if enabled 24 | animationEasing: undefined, // easing of animation if enabled 25 | boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 26 | ready: function(){}, // on layoutready 27 | stop: function(){} // on layoutstop 28 | }; 29 | 30 | // constructor 31 | // options : object containing layout options 32 | function DagreLayout( options ){ 33 | var opts = this.options = {}; 34 | for( var i in defaults ){ opts[i] = defaults[i]; } 35 | for( var i in options ){ opts[i] = options[i]; } 36 | } 37 | 38 | // runs the layout 39 | DagreLayout.prototype.run = function(){ 40 | var options = this.options; 41 | var layout = this; 42 | 43 | var cy = options.cy; // cy is automatically populated for us in the constructor 44 | var eles = options.eles; 45 | 46 | var getVal = function( ele, val ){ 47 | return isFunction(val) ? val.apply( ele, [ ele ] ) : val; 48 | }; 49 | 50 | var bb = options.boundingBox || { x1: 0, y1: 0, w: cy.width(), h: cy.height() }; 51 | if( bb.x2 === undefined ){ bb.x2 = bb.x1 + bb.w; } 52 | if( bb.w === undefined ){ bb.w = bb.x2 - bb.x1; } 53 | if( bb.y2 === undefined ){ bb.y2 = bb.y1 + bb.h; } 54 | if( bb.h === undefined ){ bb.h = bb.y2 - bb.y1; } 55 | 56 | var g = new dagre.graphlib.Graph({ 57 | multigraph: true, 58 | compound: true 59 | }); 60 | 61 | var gObj = {}; 62 | var setGObj = function( name, val ){ 63 | if( val != null ){ 64 | gObj[ name ] = val; 65 | } 66 | }; 67 | 68 | setGObj( 'nodesep', options.nodeSep ); 69 | setGObj( 'edgesep', options.edgeSep ); 70 | setGObj( 'ranksep', options.rankSep ); 71 | setGObj( 'rankdir', options.rankDir ); 72 | 73 | g.setGraph( gObj ); 74 | 75 | g.setDefaultEdgeLabel(function() { return {}; }); 76 | g.setDefaultNodeLabel(function() { return {}; }); 77 | 78 | // add nodes to dagre 79 | var nodes = eles.nodes(); 80 | for( var i = 0; i < nodes.length; i++ ){ 81 | var node = nodes[i]; 82 | var nbb = node.boundingBox(); 83 | 84 | g.setNode( node.id(), { 85 | width: nbb.w, 86 | height: nbb.h, 87 | name: node.id() 88 | } ); 89 | 90 | // console.log( g.node(node.id()) ); 91 | } 92 | 93 | // set compound parents 94 | for( var i = 0; i < nodes.length; i++ ){ 95 | var node = nodes[i]; 96 | 97 | if( node.isChild() ){ 98 | g.setParent( node.id(), node.parent().id() ); 99 | } 100 | } 101 | 102 | // add edges to dagre 103 | var edges = eles.edges().stdFilter(function( edge ){ 104 | return !edge.source().isParent() && !edge.target().isParent(); // dagre can't handle edges on compound nodes 105 | }); 106 | for( var i = 0; i < edges.length; i++ ){ 107 | var edge = edges[i]; 108 | 109 | g.setEdge( edge.source().id(), edge.target().id(), { 110 | minlen: getVal( edge, options.minLen ), 111 | weight: getVal( edge, options.edgeWeight ), 112 | name: edge.id() 113 | }, edge.id() ); 114 | 115 | // console.log( g.edge(edge.source().id(), edge.target().id(), edge.id()) ); 116 | } 117 | 118 | dagre.layout( g ); 119 | 120 | var gNodeIds = g.nodes(); 121 | for( var i = 0; i < gNodeIds.length; i++ ){ 122 | var id = gNodeIds[i]; 123 | var n = g.node( id ); 124 | 125 | cy.getElementById(id).scratch().dagre = n; 126 | } 127 | 128 | var dagreBB; 129 | 130 | if( options.boundingBox ){ 131 | dagreBB = { x1: Infinity, x2: -Infinity, y1: Infinity, y2: -Infinity }; 132 | nodes.forEach(function( node ){ 133 | var dModel = node.scratch().dagre; 134 | 135 | dagreBB.x1 = Math.min( dagreBB.x1, dModel.x ); 136 | dagreBB.x2 = Math.max( dagreBB.x2, dModel.x ); 137 | 138 | dagreBB.y1 = Math.min( dagreBB.y1, dModel.y ); 139 | dagreBB.y2 = Math.max( dagreBB.y2, dModel.y ); 140 | }); 141 | 142 | dagreBB.w = dagreBB.x2 - dagreBB.x1; 143 | dagreBB.h = dagreBB.y2 - dagreBB.y1; 144 | } else { 145 | dagreBB = bb; 146 | } 147 | 148 | var constrainPos = function( p ){ 149 | if( options.boundingBox ){ 150 | var xPct = (p.x - dagreBB.x1) / dagreBB.w; 151 | var yPct = (p.y - dagreBB.y1) / dagreBB.h; 152 | 153 | return { 154 | x: bb.x1 + xPct * bb.w, 155 | y: bb.y1 + yPct * bb.h 156 | }; 157 | } else { 158 | return p; 159 | } 160 | }; 161 | 162 | nodes.layoutPositions(layout, options, function(){ 163 | var dModel = this.scratch().dagre; 164 | 165 | return constrainPos({ 166 | x: dModel.x, 167 | y: dModel.y 168 | }); 169 | }); 170 | 171 | return this; // chaining 172 | }; 173 | 174 | cytoscape('layout', 'dagre', DagreLayout); 175 | 176 | }; 177 | 178 | if( typeof module !== 'undefined' && module.exports ){ // expose as a commonjs module 179 | module.exports = register; 180 | } 181 | 182 | if( typeof define !== 'undefined' && define.amd ){ // expose as an amd/requirejs module 183 | define("scripts/cytoscape/cytoscape-dagre", function () { 184 | return register; 185 | }); 186 | } 187 | 188 | if( typeof cytoscape !== 'undefined' && typeof dagre !== 'undefined' ){ // expose to global cytoscape (i.e. window.cytoscape) 189 | register( cytoscape, dagre ); 190 | } 191 | return register; 192 | })(); 193 | -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/scripts/lib/VSS.SDK.js: -------------------------------------------------------------------------------- 1 | //dependencies= 2 | // Copyright (C) Microsoft Corporation. All rights reserved. 3 | /// 4 | /// 5 | /// 6 | /// 7 | /// 8 | /// This file is going to be embedded into the following typescript files in the build time: 9 | /// - VSS/SDK/XDM.ts 10 | /// - VSS/SDK/VSS.SDK.ts 11 | /// This module is unlike other modules which doesn't use AMD loading. 12 | var XDM; 13 | (function (XDM) { 14 | /** 15 | * Create a new deferred object 16 | */ 17 | function createDeferred() { 18 | return new XdmDeferred(); 19 | } 20 | XDM.createDeferred = createDeferred; 21 | var XdmDeferred = (function () { 22 | function XdmDeferred() { 23 | var _this = this; 24 | this._resolveCallbacks = []; 25 | this._rejectCallbacks = []; 26 | this._isResolved = false; 27 | this._isRejected = false; 28 | this.resolve = function (result) { 29 | _this._resolve(result); 30 | }; 31 | this.reject = function (reason) { 32 | _this._reject(reason); 33 | }; 34 | this.promise = {}; 35 | this.promise.then = function (onFulfill, onReject) { 36 | return _this._then(onFulfill, onReject); 37 | }; 38 | } 39 | XdmDeferred.prototype._then = function (onFulfill, onReject) { 40 | var _this = this; 41 | if ((!onFulfill && !onReject) || 42 | (this._isResolved && !onFulfill) || 43 | (this._isRejected && !onReject)) { 44 | return this.promise; 45 | } 46 | var newDeferred = new XdmDeferred(); 47 | this._resolveCallbacks.push(function (value) { 48 | _this._wrapCallback(onFulfill, value, newDeferred, false); 49 | }); 50 | this._rejectCallbacks.push(function (reason) { 51 | _this._wrapCallback(onReject, reason, newDeferred, true); 52 | }); 53 | if (this._isResolved) { 54 | this._resolve(this._resolvedValue); 55 | } 56 | else if (this._isRejected) { 57 | this._reject(this._rejectValue); 58 | } 59 | return newDeferred.promise; 60 | }; 61 | XdmDeferred.prototype._wrapCallback = function (callback, value, deferred, reject) { 62 | if (!callback) { 63 | if (reject) { 64 | deferred.reject(value); 65 | } 66 | else { 67 | deferred.resolve(value); 68 | } 69 | return; 70 | } 71 | var result; 72 | try { 73 | result = callback(value); 74 | } 75 | catch (ex) { 76 | deferred.reject(ex); 77 | return; 78 | } 79 | if (result === undefined) { 80 | deferred.resolve(value); 81 | } 82 | else if (result && typeof result.then === "function") { 83 | result.then(function (innerResult) { 84 | deferred.resolve(innerResult); 85 | }, function (innerReason) { 86 | deferred.reject(innerReason); 87 | }); 88 | } 89 | else { 90 | deferred.resolve(result); 91 | } 92 | }; 93 | XdmDeferred.prototype._resolve = function (result) { 94 | if (!this._isRejected && !this._isResolved) { 95 | this._isResolved = true; 96 | this._resolvedValue = result; 97 | } 98 | if (this._isResolved && this._resolveCallbacks.length > 0) { 99 | var resolveCallbacks = this._resolveCallbacks.splice(0); 100 | // 2.2.4. #onFulfilled or onRejected must not be called until the execution context stack contains only platform code. 101 | window.setTimeout(function () { 102 | for (var i = 0, l = resolveCallbacks.length; i < l; i++) { 103 | resolveCallbacks[i](result); 104 | } 105 | }); 106 | } 107 | }; 108 | XdmDeferred.prototype._reject = function (reason) { 109 | if (!this._isRejected && !this._isResolved) { 110 | this._isRejected = true; 111 | this._rejectValue = reason; 112 | if (this._rejectCallbacks.length === 0 && window.console && window.console.warn) { 113 | console.warn("Rejected XDM promise with no reject callbacks"); 114 | if (reason) { 115 | console.warn(reason); 116 | } 117 | } 118 | } 119 | if (this._isRejected && this._rejectCallbacks.length > 0) { 120 | var rejectCallbacks = this._rejectCallbacks.splice(0); 121 | // 2.2.4. #onFulfilled or onRejected must not be called until the execution context stack contains only platform code. 122 | window.setTimeout(function () { 123 | for (var i = 0, l = rejectCallbacks.length; i < l; i++) { 124 | rejectCallbacks[i](reason); 125 | } 126 | }); 127 | } 128 | }; 129 | return XdmDeferred; 130 | }()); 131 | var smallestRandom = parseInt("10000000000", 36); 132 | var maxSafeInteger = Number.MAX_SAFE_INTEGER || 9007199254740991; 133 | /** 134 | * Create a new random 22-character fingerprint. 135 | * @return string fingerprint 136 | */ 137 | function newFingerprint() { 138 | // smallestRandom ensures we will get a 11-character result from the base-36 conversion. 139 | return Math.floor((Math.random() * (maxSafeInteger - smallestRandom)) + smallestRandom).toString(36) + 140 | Math.floor((Math.random() * (maxSafeInteger - smallestRandom)) + smallestRandom).toString(36); 141 | } 142 | /** 143 | * Catalog of objects exposed for XDM 144 | */ 145 | var XDMObjectRegistry = (function () { 146 | function XDMObjectRegistry() { 147 | this._registeredObjects = {}; 148 | } 149 | /** 150 | * Register an object (instance or factory method) exposed by this frame to callers in a remote frame 151 | * 152 | * @param instanceId unique id of the registered object 153 | * @param instance Either: (1) an object instance, or (2) a function that takes optional context data and returns an object instance. 154 | */ 155 | XDMObjectRegistry.prototype.register = function (instanceId, instance) { 156 | this._registeredObjects[instanceId] = instance; 157 | }; 158 | /** 159 | * Get an instance of an object registered with the given id 160 | * 161 | * @param instanceId unique id of the registered object 162 | * @param contextData Optional context data to pass to a registered object's factory method 163 | */ 164 | XDMObjectRegistry.prototype.getInstance = function (instanceId, contextData) { 165 | var instance = this._registeredObjects[instanceId]; 166 | if (!instance) { 167 | return null; 168 | } 169 | if (typeof instance === "function") { 170 | return instance(contextData); 171 | } 172 | else { 173 | return instance; 174 | } 175 | }; 176 | return XDMObjectRegistry; 177 | }()); 178 | XDM.XDMObjectRegistry = XDMObjectRegistry; 179 | ; 180 | /** 181 | * The registry of global XDM handlers 182 | */ 183 | XDM.globalObjectRegistry = new XDMObjectRegistry(); 184 | /** 185 | * Represents a channel of communication between frames\document 186 | * Stays "alive" across multiple funtion\method calls 187 | */ 188 | var XDMChannel = (function () { 189 | function XDMChannel(postToWindow, targetOrigin) { 190 | if (targetOrigin === void 0) { targetOrigin = null; } 191 | this._nextMessageId = 1; 192 | this._deferreds = {}; 193 | this._nextProxyFunctionId = 1; 194 | this._proxyFunctions = {}; 195 | this._postToWindow = postToWindow; 196 | this._targetOrigin = targetOrigin; 197 | this._channelObjectRegistry = new XDMObjectRegistry(); 198 | this._channelId = XDMChannel._nextChannelId++; 199 | if (!this._targetOrigin) { 200 | this._handshakeToken = newFingerprint(); 201 | } 202 | } 203 | /** 204 | * Get the object registry to handle messages from this specific channel. 205 | * Upon receiving a message, this channel registry will be used first, then 206 | * the global registry will be used if no handler is found here. 207 | */ 208 | XDMChannel.prototype.getObjectRegistry = function () { 209 | return this._channelObjectRegistry; 210 | }; 211 | /** 212 | * Invoke a method via RPC. Lookup the registered object on the remote end of the channel and invoke the specified method. 213 | * 214 | * @param method Name of the method to invoke 215 | * @param instanceId unique id of the registered object 216 | * @param params Arguments to the method to invoke 217 | * @param instanceContextData Optional context data to pass to a registered object's factory method 218 | * @param serializationSettings Optional serialization settings 219 | */ 220 | XDMChannel.prototype.invokeRemoteMethod = function (methodName, instanceId, params, instanceContextData, serializationSettings) { 221 | var message = { 222 | id: this._nextMessageId++, 223 | methodName: methodName, 224 | instanceId: instanceId, 225 | instanceContext: instanceContextData, 226 | params: this._customSerializeObject(params, serializationSettings), 227 | jsonrpc: "2.0", 228 | serializationSettings: serializationSettings 229 | }; 230 | if (!this._targetOrigin) { 231 | message.handshakeToken = this._handshakeToken; 232 | } 233 | var deferred = createDeferred(); 234 | this._deferreds[message.id] = deferred; 235 | this._sendRpcMessage(message); 236 | return deferred.promise; 237 | }; 238 | /** 239 | * Get a proxied object that represents the object registered with the given instance id on the remote side of this channel. 240 | * 241 | * @param instanceId unique id of the registered object 242 | * @param contextData Optional context data to pass to a registered object's factory method 243 | */ 244 | XDMChannel.prototype.getRemoteObjectProxy = function (instanceId, contextData) { 245 | return this.invokeRemoteMethod(null, instanceId, null, contextData); 246 | }; 247 | XDMChannel.prototype.invokeMethod = function (registeredInstance, rpcMessage) { 248 | var _this = this; 249 | if (!rpcMessage.methodName) { 250 | // Null/empty method name indicates to return the registered object itself. 251 | this._success(rpcMessage, registeredInstance, rpcMessage.handshakeToken); 252 | return; 253 | } 254 | var method = registeredInstance[rpcMessage.methodName]; 255 | if (typeof method !== "function") { 256 | this._error(rpcMessage, new Error("RPC method not found: " + rpcMessage.methodName), rpcMessage.handshakeToken); 257 | return; 258 | } 259 | try { 260 | // Call specified method. Add nested success and error call backs with closure 261 | // so we can post back a response as a result or error as appropriate 262 | var methodArgs = []; 263 | if (rpcMessage.params) { 264 | methodArgs = this._customDeserializeObject(rpcMessage.params); 265 | } 266 | var result = method.apply(registeredInstance, methodArgs); 267 | if (result && result.then && typeof result.then === "function") { 268 | result.then(function (asyncResult) { 269 | _this._success(rpcMessage, asyncResult, rpcMessage.handshakeToken); 270 | }, function (e) { 271 | _this._error(rpcMessage, e, rpcMessage.handshakeToken); 272 | }); 273 | } 274 | else { 275 | this._success(rpcMessage, result, rpcMessage.handshakeToken); 276 | } 277 | } 278 | catch (exception) { 279 | // send back as error if an exception is thrown 280 | this._error(rpcMessage, exception, rpcMessage.handshakeToken); 281 | } 282 | }; 283 | XDMChannel.prototype.getRegisteredObject = function (instanceId, instanceContext) { 284 | if (instanceId === "__proxyFunctions") { 285 | // Special case for proxied functions of remote instances 286 | return this._proxyFunctions; 287 | } 288 | // Look in the channel registry first 289 | var registeredObject = this._channelObjectRegistry.getInstance(instanceId, instanceContext); 290 | if (!registeredObject) { 291 | // Look in the global registry as a fallback 292 | registeredObject = XDM.globalObjectRegistry.getInstance(instanceId, instanceContext); 293 | } 294 | return registeredObject; 295 | }; 296 | /** 297 | * Handle a received message on this channel. Dispatch to the appropriate object found via object registry 298 | * 299 | * @param data Message data 300 | * @param origin Origin of the frame that sent the message 301 | * @return True if the message was handled by this channel. Otherwise false. 302 | */ 303 | XDMChannel.prototype.onMessage = function (data, origin) { 304 | var _this = this; 305 | var rpcMessage = data; 306 | if (rpcMessage.instanceId) { 307 | // Find the object that handles this requestNeed to find implementation 308 | // Look in the channel registry first 309 | var registeredObject = this.getRegisteredObject(rpcMessage.instanceId, rpcMessage.instanceContext); 310 | if (!registeredObject) { 311 | // If not found return false to indicate that the message was not handled 312 | return false; 313 | } 314 | if (typeof registeredObject["then"] === "function") { 315 | registeredObject.then(function (resolvedInstance) { 316 | _this.invokeMethod(resolvedInstance, rpcMessage); 317 | }, function (e) { 318 | _this._error(rpcMessage, e, rpcMessage.handshakeToken); 319 | }); 320 | } 321 | else { 322 | this.invokeMethod(registeredObject, rpcMessage); 323 | } 324 | } 325 | else { 326 | // response 327 | // Responses look like this - 328 | // {"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"} 329 | // {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found."}, "id": "5"} 330 | var deferred = this._deferreds[rpcMessage.id]; 331 | if (!deferred) { 332 | // Message not handled by this channel. 333 | return false; 334 | } 335 | if (rpcMessage.error) { 336 | deferred.reject(this._customDeserializeObject([rpcMessage.error])[0]); 337 | } 338 | else { 339 | deferred.resolve(this._customDeserializeObject([rpcMessage.result])[0]); 340 | } 341 | delete this._deferreds[rpcMessage.id]; 342 | } 343 | // Message handled by this channel 344 | return true; 345 | }; 346 | XDMChannel.prototype.owns = function (source, origin, data) { 347 | /// Determines whether the current message belongs to this channel or not 348 | var rpcMessage = data; 349 | if (this._postToWindow === source) { 350 | // For messages coming from sandboxed iframes the origin will be set to the string "null". This is 351 | // how onprem works. If it is not a sandboxed iFrame we will get the origin as expected. 352 | if (this._targetOrigin) { 353 | if (origin) { 354 | return origin.toLowerCase() === "null" || this._targetOrigin.toLowerCase().indexOf(origin.toLowerCase()) === 0; 355 | } 356 | else { 357 | return false; 358 | } 359 | } 360 | else { 361 | if (rpcMessage.handshakeToken && rpcMessage.handshakeToken === this._handshakeToken) { 362 | this._targetOrigin = origin; 363 | return true; 364 | } 365 | } 366 | } 367 | return false; 368 | }; 369 | XDMChannel.prototype.error = function (data, errorObj) { 370 | var rpcMessage = data; 371 | this._error(rpcMessage, errorObj, rpcMessage.handshakeToken); 372 | }; 373 | XDMChannel.prototype._error = function (messageObj, errorObj, handshakeToken) { 374 | // Post back a response as an error which look like this - 375 | // {"id": "5", "error": {"code": -32601, "message": "Method not found."}, "jsonrpc": "2.0", } 376 | var message = { 377 | id: messageObj.id, 378 | error: this._customSerializeObject([errorObj], messageObj.serializationSettings)[0], 379 | jsonrpc: "2.0", 380 | handshakeToken: handshakeToken 381 | }; 382 | this._sendRpcMessage(message); 383 | }; 384 | XDMChannel.prototype._success = function (messageObj, result, handshakeToken) { 385 | // Post back response result which look like this - 386 | // {"id": "9", "result": ["hello", 5], "jsonrpc": "2.0"} 387 | var message = { 388 | id: messageObj.id, 389 | result: this._customSerializeObject([result], messageObj.serializationSettings)[0], 390 | jsonrpc: "2.0", 391 | handshakeToken: handshakeToken 392 | }; 393 | this._sendRpcMessage(message); 394 | }; 395 | XDMChannel.prototype._sendRpcMessage = function (message) { 396 | var messageString = JSON.stringify(message); 397 | this._postToWindow.postMessage(messageString, "*"); 398 | }; 399 | XDMChannel.prototype._shouldSkipSerialization = function (obj) { 400 | for (var i = 0, l = XDMChannel.WINDOW_TYPES_TO_SKIP_SERIALIZATION.length; i < l; i++) { 401 | var instanceType = XDMChannel.WINDOW_TYPES_TO_SKIP_SERIALIZATION[i]; 402 | if (window[instanceType] && obj instanceof window[instanceType]) { 403 | return true; 404 | } 405 | } 406 | if (window.jQuery) { 407 | for (var i = 0, l = XDMChannel.JQUERY_TYPES_TO_SKIP_SERIALIZATION.length; i < l; i++) { 408 | var instanceType = XDMChannel.JQUERY_TYPES_TO_SKIP_SERIALIZATION[i]; 409 | if (window.jQuery[instanceType] && obj instanceof window.jQuery[instanceType]) { 410 | return true; 411 | } 412 | } 413 | } 414 | return false; 415 | }; 416 | XDMChannel.prototype._customSerializeObject = function (obj, settings, parentObjects, nextCircularRefId, depth) { 417 | var _this = this; 418 | if (parentObjects === void 0) { parentObjects = null; } 419 | if (nextCircularRefId === void 0) { nextCircularRefId = 1; } 420 | if (depth === void 0) { depth = 1; } 421 | if (!obj || depth > XDMChannel.MAX_XDM_DEPTH) { 422 | return null; 423 | } 424 | if (this._shouldSkipSerialization(obj)) { 425 | return null; 426 | } 427 | var serializeMember = function (parentObject, newObject, key) { 428 | var item; 429 | try { 430 | item = parentObject[key]; 431 | } 432 | catch (ex) { 433 | } 434 | var itemType = typeof item; 435 | if (itemType === "undefined") { 436 | return; 437 | } 438 | // Check for a circular reference by looking at parent objects 439 | var parentItemIndex = -1; 440 | if (itemType === "object") { 441 | parentItemIndex = parentObjects.originalObjects.indexOf(item); 442 | } 443 | if (parentItemIndex >= 0) { 444 | // Circular reference found. Add reference to parent 445 | var parentItem = parentObjects.newObjects[parentItemIndex]; 446 | if (!parentItem.__circularReferenceId) { 447 | parentItem.__circularReferenceId = nextCircularRefId++; 448 | } 449 | newObject[key] = { 450 | __circularReference: parentItem.__circularReferenceId 451 | }; 452 | } 453 | else { 454 | if (itemType === "function") { 455 | var proxyFunctionId = _this._nextProxyFunctionId++; 456 | newObject[key] = { 457 | __proxyFunctionId: _this._registerProxyFunction(item, obj), 458 | __channelId: _this._channelId 459 | }; 460 | } 461 | else if (itemType === "object") { 462 | if (item && item instanceof Date) { 463 | newObject[key] = { 464 | __proxyDate: item.getTime() 465 | }; 466 | } 467 | else { 468 | newObject[key] = _this._customSerializeObject(item, settings, parentObjects, nextCircularRefId, depth + 1); 469 | } 470 | } 471 | else if (key !== "__proxyFunctionId") { 472 | // Just add non object/function properties as-is. Don't include "__proxyFunctionId" to protect 473 | // our proxy methods from being invoked from other messages. 474 | newObject[key] = item; 475 | } 476 | } 477 | }; 478 | var returnValue; 479 | if (!parentObjects) { 480 | parentObjects = { 481 | newObjects: [], 482 | originalObjects: [] 483 | }; 484 | } 485 | parentObjects.originalObjects.push(obj); 486 | if (obj instanceof Array) { 487 | returnValue = []; 488 | parentObjects.newObjects.push(returnValue); 489 | for (var i = 0, l = obj.length; i < l; i++) { 490 | serializeMember(obj, returnValue, i); 491 | } 492 | } 493 | else { 494 | returnValue = {}; 495 | parentObjects.newObjects.push(returnValue); 496 | var keys = {}; 497 | try { 498 | // We want to get both enumerable and non-enumerable properties 499 | // including inherited enumerable properties. for..in grabs 500 | // enumerable properties (including inherited properties) and 501 | // getOwnPropertyNames includes non-enumerable properties. 502 | // Merge these results together. 503 | for (var key in obj) { 504 | keys[key] = true; 505 | } 506 | var ownProperties = Object.getOwnPropertyNames(obj); 507 | for (var i = 0, l = ownProperties.length; i < l; i++) { 508 | keys[ownProperties[i]] = true; 509 | } 510 | } 511 | catch (ex) { 512 | } 513 | for (var key in keys) { 514 | // Don't serialize properties that start with an underscore. 515 | if ((key && key[0] !== "_") || (settings && settings.includeUnderscoreProperties)) { 516 | serializeMember(obj, returnValue, key); 517 | } 518 | } 519 | } 520 | parentObjects.originalObjects.pop(); 521 | parentObjects.newObjects.pop(); 522 | return returnValue; 523 | }; 524 | XDMChannel.prototype._registerProxyFunction = function (func, context) { 525 | var proxyFunctionId = this._nextProxyFunctionId++; 526 | this._proxyFunctions["proxy" + proxyFunctionId] = function () { 527 | return func.apply(context, Array.prototype.slice.call(arguments, 0)); 528 | }; 529 | return proxyFunctionId; 530 | }; 531 | XDMChannel.prototype._customDeserializeObject = function (obj, circularRefs) { 532 | var _this = this; 533 | var that = this; 534 | if (!obj) { 535 | return null; 536 | } 537 | if (!circularRefs) { 538 | circularRefs = {}; 539 | } 540 | var deserializeMember = function (parentObject, key) { 541 | var item = parentObject[key]; 542 | var itemType = typeof item; 543 | if (key === "__circularReferenceId" && itemType === 'number') { 544 | circularRefs[item] = parentObject; 545 | delete parentObject[key]; 546 | } 547 | else if (itemType === "object" && item) { 548 | if (item.__proxyFunctionId) { 549 | parentObject[key] = function () { 550 | return that.invokeRemoteMethod("proxy" + item.__proxyFunctionId, "__proxyFunctions", Array.prototype.slice.call(arguments, 0), null, { includeUnderscoreProperties: true }); 551 | }; 552 | } 553 | else if (item.__proxyDate) { 554 | parentObject[key] = new Date(item.__proxyDate); 555 | } 556 | else if (item.__circularReference) { 557 | parentObject[key] = circularRefs[item.__circularReference]; 558 | } 559 | else { 560 | _this._customDeserializeObject(item, circularRefs); 561 | } 562 | } 563 | }; 564 | if (obj instanceof Array) { 565 | for (var i = 0, l = obj.length; i < l; i++) { 566 | deserializeMember(obj, i); 567 | } 568 | } 569 | else if (typeof obj === "object") { 570 | for (var key in obj) { 571 | deserializeMember(obj, key); 572 | } 573 | } 574 | return obj; 575 | }; 576 | XDMChannel._nextChannelId = 1; 577 | XDMChannel.MAX_XDM_DEPTH = 100; 578 | XDMChannel.WINDOW_TYPES_TO_SKIP_SERIALIZATION = [ 579 | "Node", 580 | "Window", 581 | "Event" 582 | ]; 583 | XDMChannel.JQUERY_TYPES_TO_SKIP_SERIALIZATION = [ 584 | "jQuery" 585 | ]; 586 | return XDMChannel; 587 | }()); 588 | XDM.XDMChannel = XDMChannel; 589 | /** 590 | * Registry of XDM channels kept per target frame/window 591 | */ 592 | var XDMChannelManager = (function () { 593 | function XDMChannelManager() { 594 | this._channels = []; 595 | this._subscribe(window); 596 | } 597 | XDMChannelManager.get = function () { 598 | if (!this._default) { 599 | this._default = new XDMChannelManager(); 600 | } 601 | return this._default; 602 | }; 603 | /** 604 | * Add an XDM channel for the given target window/iframe 605 | * 606 | * @param window Target iframe window to communicate with 607 | * @param targetOrigin Url of the target iframe (if known) 608 | */ 609 | XDMChannelManager.prototype.addChannel = function (window, targetOrigin) { 610 | var channel = new XDMChannel(window, targetOrigin); 611 | this._channels.push(channel); 612 | return channel; 613 | }; 614 | XDMChannelManager.prototype._handleMessageReceived = function (event) { 615 | // get channel and dispatch to it 616 | var i, len, channel; 617 | var rpcMessage; 618 | if (typeof event.data === "string") { 619 | try { 620 | rpcMessage = JSON.parse(event.data); 621 | } 622 | catch (error) { 623 | } 624 | } 625 | if (rpcMessage) { 626 | var handled = false; 627 | var channelOwner; 628 | for (i = 0, len = this._channels.length; i < len; i++) { 629 | channel = this._channels[i]; 630 | if (channel.owns(event.source, event.origin, rpcMessage)) { 631 | // keep a reference to the channel owner found. 632 | channelOwner = channel; 633 | handled = channel.onMessage(rpcMessage, event.origin) || handled; 634 | } 635 | } 636 | if (!!channelOwner && !handled) { 637 | if (window.console) { 638 | console.error("No handler found on any channel for message: " + JSON.stringify(rpcMessage)); 639 | } 640 | // for instance based proxies, send an error on the channel owning the message to resolve any control creation promises 641 | // on the host frame. 642 | if (rpcMessage.instanceId) { 643 | channelOwner.error(rpcMessage, "The registered object " + rpcMessage.instanceId + " could not be found."); 644 | } 645 | } 646 | } 647 | }; 648 | XDMChannelManager.prototype._subscribe = function (windowObj) { 649 | var _this = this; 650 | if (windowObj.addEventListener) { 651 | windowObj.addEventListener("message", function (event) { 652 | _this._handleMessageReceived(event); 653 | }); 654 | } 655 | else { 656 | // IE8 657 | windowObj.attachEvent("onmessage", function (event) { 658 | _this._handleMessageReceived(event); 659 | }); 660 | } 661 | }; 662 | return XDMChannelManager; 663 | }()); 664 | XDM.XDMChannelManager = XDMChannelManager; 665 | })(XDM || (XDM = {})); 666 | var VSS; 667 | (function (VSS) { 668 | // W A R N I N G: if VssSDKVersion changes, the VSS WEB SDK demand resolver needs to be updated with the new version 669 | VSS.VssSDKVersion = 2.0; 670 | VSS.VssSDKRestVersion = "2.2"; 671 | var bodyElement; 672 | var webContext; 673 | var hostPageContext; 674 | var extensionContext; 675 | var initialConfiguration; 676 | var initialContribution; 677 | var initOptions; 678 | var loaderConfigured = false; 679 | var usingPlatformScripts; 680 | var usingPlatformStyles; 681 | var isReady = false; 682 | var readyCallbacks; 683 | var parentChannel = XDM.XDMChannelManager.get().addChannel(window.parent); 684 | var shimmedLocalStorage; 685 | var hostReadyForShimUpdates = false; 686 | var Storage = (function () { 687 | var changeCallback; 688 | function invokeChangeCallback() { 689 | if (changeCallback) { 690 | changeCallback.call(this); 691 | } 692 | } 693 | function Storage(changeCallback) { 694 | } 695 | Object.defineProperties(Storage.prototype, { 696 | getItem: { 697 | get: function () { 698 | return function (key) { 699 | var item = this["" + key]; 700 | return typeof item === "undefined" ? null : item; 701 | }; 702 | } 703 | }, 704 | setItem: { 705 | get: function () { 706 | return function (key, value) { 707 | key = "" + key; 708 | var existingValue = this[key]; 709 | var newValue = "" + value; 710 | if (existingValue !== newValue) { 711 | this[key] = newValue; 712 | invokeChangeCallback(); 713 | } 714 | }; 715 | } 716 | }, 717 | removeItem: { 718 | get: function () { 719 | return function (key) { 720 | key = "" + key; 721 | if (typeof this[key] !== "undefined") { 722 | delete this[key]; 723 | invokeChangeCallback(); 724 | } 725 | }; 726 | } 727 | }, 728 | clear: { 729 | get: function () { 730 | return function () { 731 | var keys = Object.keys(this); 732 | if (keys.length > 0) { 733 | for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) { 734 | var key = keys_1[_i]; 735 | delete this[key]; 736 | } 737 | invokeChangeCallback(); 738 | } 739 | }; 740 | } 741 | }, 742 | key: { 743 | get: function () { 744 | return function (index) { 745 | return Object.keys(this)[index]; 746 | }; 747 | } 748 | }, 749 | length: { 750 | get: function () { 751 | return Object.keys(this).length; 752 | } 753 | } 754 | }); 755 | return Storage; 756 | }()); 757 | function shimSandboxedProperties() { 758 | var updateSettingsTimeout; 759 | function updateShimmedStorageCallback() { 760 | // Talk to the host frame on a 50 ms delay in order to batch storage/cookie updates 761 | if (!updateSettingsTimeout) { 762 | updateSettingsTimeout = setTimeout(function () { 763 | updateSettingsTimeout = 0; 764 | updateHostSandboxedStorage(); 765 | }, 50); 766 | } 767 | } 768 | // Override document.cookie if it is not available 769 | var hasCookieSupport = false; 770 | try { 771 | hasCookieSupport = typeof document.cookie === "string"; 772 | } 773 | catch (ex) { 774 | } 775 | if (!hasCookieSupport) { 776 | Object.defineProperty(Document.prototype, "cookie", { 777 | get: function () { 778 | return ""; 779 | }, 780 | set: function (value) { 781 | } 782 | }); 783 | } 784 | // Override browser storage 785 | var hasLocalStorage = false; 786 | try { 787 | hasLocalStorage = !!window.localStorage; 788 | } 789 | catch (ex) { 790 | } 791 | if (!hasLocalStorage) { 792 | delete window.localStorage; 793 | shimmedLocalStorage = new Storage(updateShimmedStorageCallback); 794 | Object.defineProperty(window, "localStorage", { value: shimmedLocalStorage }); 795 | delete window.sessionStorage; 796 | Object.defineProperty(window, "sessionStorage", { value: new Storage() }); 797 | } 798 | } 799 | if (!window["__vssNoSandboxShim"]) { 800 | try { 801 | shimSandboxedProperties(); 802 | } 803 | catch (ex) { 804 | if (window.console && window.console.warn) { 805 | window.console.warn("Failed to shim support for sandboxed properties: " + ex.message + ". Set \"window.__vssNoSandboxShim = true\" in order to bypass the shim of sandboxed properties."); 806 | } 807 | } 808 | } 809 | /** 810 | * Service Ids for core services (to be used in VSS.getService) 811 | */ 812 | var ServiceIds; 813 | (function (ServiceIds) { 814 | /** 815 | * Service for showing dialogs in the host frame 816 | * Use: 817 | */ 818 | ServiceIds.Dialog = "ms.vss-web.dialog-service"; 819 | /** 820 | * Service for interacting with the host frame's navigation (getting/updating the address/hash, reloading the page, etc.) 821 | * Use: 822 | */ 823 | ServiceIds.Navigation = "ms.vss-web.navigation-service"; 824 | /** 825 | * Service for interacting with extension data (setting/setting documents and collections) 826 | * Use: 827 | */ 828 | ServiceIds.ExtensionData = "ms.vss-web.data-service"; 829 | })(ServiceIds = VSS.ServiceIds || (VSS.ServiceIds = {})); 830 | /** 831 | * Initiates the handshake with the host window. 832 | * 833 | * @param options Initialization options for the extension. 834 | */ 835 | function init(options) { 836 | initOptions = options || {}; 837 | usingPlatformScripts = initOptions.usePlatformScripts; 838 | usingPlatformStyles = initOptions.usePlatformStyles; 839 | // Run this after current execution path is complete - allows objects to get initialized 840 | window.setTimeout(function () { 841 | var appHandshakeData = { 842 | notifyLoadSucceeded: !initOptions.explicitNotifyLoaded, 843 | extensionReusedCallback: initOptions.extensionReusedCallback, 844 | vssSDKVersion: VSS.VssSDKVersion 845 | }; 846 | parentChannel.invokeRemoteMethod("initialHandshake", "VSS.HostControl", [appHandshakeData]).then(function (handshakeData) { 847 | hostPageContext = handshakeData.pageContext; 848 | webContext = hostPageContext.webContext; 849 | initialConfiguration = handshakeData.initialConfig || {}; 850 | initialContribution = handshakeData.contribution; 851 | extensionContext = handshakeData.extensionContext; 852 | if (handshakeData.sandboxedStorage) { 853 | var updateNeeded = false; 854 | if (shimmedLocalStorage) { 855 | if (handshakeData.sandboxedStorage.localStorage) { 856 | // Merge host data in with any values already set. 857 | var newData = handshakeData.sandboxedStorage.localStorage; 858 | // Check for any properties written prior to the initial handshake 859 | for (var _i = 0, _a = Object.keys(shimmedLocalStorage); _i < _a.length; _i++) { 860 | var key = _a[_i]; 861 | var value = shimmedLocalStorage.getItem(key); 862 | if (value !== newData[key]) { 863 | newData[key] = value; 864 | updateNeeded = true; 865 | } 866 | } 867 | // Update the stored values 868 | for (var _b = 0, _c = Object.keys(newData); _b < _c.length; _b++) { 869 | var key = _c[_b]; 870 | shimmedLocalStorage.setItem(key, newData[key]); 871 | } 872 | } 873 | else if (shimmedLocalStorage.length > 0) { 874 | updateNeeded = true; 875 | } 876 | } 877 | hostReadyForShimUpdates = true; 878 | if (updateNeeded) { 879 | // Talk to host frame to issue update 880 | updateHostSandboxedStorage(); 881 | } 882 | } 883 | if (usingPlatformScripts || usingPlatformStyles) { 884 | setupAmdLoader(); 885 | } 886 | else { 887 | triggerReady(); 888 | } 889 | }); 890 | }, 0); 891 | } 892 | VSS.init = init; 893 | function updateHostSandboxedStorage() { 894 | var storage = { 895 | localStorage: JSON.stringify(shimmedLocalStorage || {}) 896 | }; 897 | parentChannel.invokeRemoteMethod("updateSandboxedStorage", "VSS.HostControl", [storage]); 898 | } 899 | /** 900 | * Ensures that the AMD loader from the host is configured and fetches a script (AMD) module 901 | * (and its dependencies). If no callback is supplied, this will still perform an asynchronous 902 | * fetch of the module (unlike AMD require which returns synchronously). This method has no return value. 903 | * 904 | * Usage: 905 | * 906 | * VSS.require(["VSS/Controls", "VSS/Controls/Grids"], function(Controls, Grids) { 907 | * ... 908 | * }); 909 | * 910 | * @param modules A single module path (string) or array of paths (string[]) 911 | * @param callback Method called once the modules have been loaded. 912 | */ 913 | function require(modules, callback) { 914 | var modulesArray; 915 | if (typeof modules === "string") { 916 | modulesArray = [modules]; 917 | } 918 | else { 919 | modulesArray = modules; 920 | } 921 | if (!callback) { 922 | // Generate an empty callback for require 923 | callback = function () { }; 924 | } 925 | if (loaderConfigured) { 926 | // Loader already configured, just issue require 927 | issueVssRequire(modulesArray, callback); 928 | } 929 | else { 930 | if (!initOptions) { 931 | init({ usePlatformScripts: true }); 932 | } 933 | else if (!usingPlatformScripts) { 934 | usingPlatformScripts = true; 935 | if (isReady) { 936 | // We are in the ready state, but previously not using the loader, so set it up now 937 | // which will re-trigger ready 938 | isReady = false; 939 | setupAmdLoader(); 940 | } 941 | } 942 | ready(function () { 943 | issueVssRequire(modulesArray, callback); 944 | }); 945 | } 946 | } 947 | VSS.require = require; 948 | function issueVssRequire(modules, callback) { 949 | if (hostPageContext.diagnostics.bundlingEnabled) { 950 | window.require(["VSS/Bundling"], function (VSS_Bundling) { 951 | VSS_Bundling.requireModules(modules, callback); 952 | }); 953 | } 954 | else { 955 | window.require(modules, callback); 956 | } 957 | } 958 | /** 959 | * Register a callback that gets called once the initial setup/handshake has completed. 960 | * If the initial setup is already completed, the callback is invoked at the end of the current call stack. 961 | */ 962 | function ready(callback) { 963 | if (isReady) { 964 | window.setTimeout(callback, 0); 965 | } 966 | else { 967 | if (!readyCallbacks) { 968 | readyCallbacks = []; 969 | } 970 | readyCallbacks.push(callback); 971 | } 972 | } 973 | VSS.ready = ready; 974 | /** 975 | * Notifies the host that the extension successfully loaded (stop showing the loading indicator) 976 | */ 977 | function notifyLoadSucceeded() { 978 | parentChannel.invokeRemoteMethod("notifyLoadSucceeded", "VSS.HostControl"); 979 | } 980 | VSS.notifyLoadSucceeded = notifyLoadSucceeded; 981 | /** 982 | * Notifies the host that the extension failed to load 983 | */ 984 | function notifyLoadFailed(e) { 985 | parentChannel.invokeRemoteMethod("notifyLoadFailed", "VSS.HostControl", [e]); 986 | } 987 | VSS.notifyLoadFailed = notifyLoadFailed; 988 | /** 989 | * Get the web context from the parent host 990 | */ 991 | function getWebContext() { 992 | return webContext; 993 | } 994 | VSS.getWebContext = getWebContext; 995 | /** 996 | * Get the configuration data passed in the initial handshake from the parent frame 997 | */ 998 | function getConfiguration() { 999 | return initialConfiguration; 1000 | } 1001 | VSS.getConfiguration = getConfiguration; 1002 | /** 1003 | * Get the context about the extension that owns the content that is being hosted 1004 | */ 1005 | function getExtensionContext() { 1006 | return extensionContext; 1007 | } 1008 | VSS.getExtensionContext = getExtensionContext; 1009 | /** 1010 | * Gets the information about the contribution that first caused this extension to load. 1011 | */ 1012 | function getContribution() { 1013 | return initialContribution; 1014 | } 1015 | VSS.getContribution = getContribution; 1016 | /** 1017 | * Get a contributed service from the parent host. 1018 | * 1019 | * @param contributionId Full Id of the service contribution to get the instance of 1020 | * @param context Optional context information to use when obtaining the service instance 1021 | */ 1022 | function getService(contributionId, context) { 1023 | return getServiceContribution(contributionId).then(function (serviceContribution) { 1024 | if (!context) { 1025 | context = {}; 1026 | } 1027 | if (!context["webContext"]) { 1028 | context["webContext"] = getWebContext(); 1029 | } 1030 | if (!context["extensionContext"]) { 1031 | context["extensionContext"] = getExtensionContext(); 1032 | } 1033 | return serviceContribution.getInstance(serviceContribution.id, context); 1034 | }); 1035 | } 1036 | VSS.getService = getService; 1037 | /** 1038 | * Get the contribution with the given contribution id. The returned contribution has a method to get a registered object within that contribution. 1039 | * 1040 | * @param contributionId Id of the contribution to get 1041 | */ 1042 | function getServiceContribution(contributionId) { 1043 | var deferred = XDM.createDeferred(); 1044 | VSS.ready(function () { 1045 | parentChannel.invokeRemoteMethod("getServiceContribution", "vss.hostManagement", [contributionId]).then(function (contribution) { 1046 | var serviceContribution = contribution; 1047 | serviceContribution.getInstance = function (objectId, context) { 1048 | return getBackgroundContributionInstance(contribution, objectId, context); 1049 | }; 1050 | deferred.resolve(serviceContribution); 1051 | }, deferred.reject); 1052 | }); 1053 | return deferred.promise; 1054 | } 1055 | VSS.getServiceContribution = getServiceContribution; 1056 | /** 1057 | * Get contributions that target a given contribution id. The returned contributions have a method to get a registered object within that contribution. 1058 | * 1059 | * @param targetContributionId Contributions that target the contribution with this id will be returned 1060 | */ 1061 | function getServiceContributions(targetContributionId) { 1062 | var deferred = XDM.createDeferred(); 1063 | VSS.ready(function () { 1064 | parentChannel.invokeRemoteMethod("getContributionsForTarget", "vss.hostManagement", [targetContributionId]).then(function (contributions) { 1065 | var serviceContributions = []; 1066 | contributions.forEach(function (contribution) { 1067 | var serviceContribution = contribution; 1068 | serviceContribution.getInstance = function (objectId, context) { 1069 | return getBackgroundContributionInstance(contribution, objectId, context); 1070 | }; 1071 | serviceContributions.push(serviceContribution); 1072 | }); 1073 | deferred.resolve(serviceContributions); 1074 | }, deferred.reject); 1075 | }); 1076 | return deferred.promise; 1077 | } 1078 | VSS.getServiceContributions = getServiceContributions; 1079 | /** 1080 | * Create an instance of a registered object within the given contribution in the host's frame 1081 | * 1082 | * @param contribution The contribution to get an object from 1083 | * @param objectId Optional id of the registered object (the contribution's id property is used by default) 1084 | * @param contextData Optional context to use when getting the object. 1085 | */ 1086 | function getBackgroundContributionInstance(contribution, objectId, contextData) { 1087 | var deferred = XDM.createDeferred(); 1088 | VSS.ready(function () { 1089 | parentChannel.invokeRemoteMethod("getBackgroundContributionInstance", "vss.hostManagement", [contribution, objectId, contextData]).then(deferred.resolve, deferred.reject); 1090 | }); 1091 | return deferred.promise; 1092 | } 1093 | /** 1094 | * Register an object (instance or factory method) that this extension exposes to the host frame. 1095 | * 1096 | * @param instanceId unique id of the registered object 1097 | * @param instance Either: (1) an object instance, or (2) a function that takes optional context data and returns an object instance. 1098 | */ 1099 | function register(instanceId, instance) { 1100 | parentChannel.getObjectRegistry().register(instanceId, instance); 1101 | } 1102 | VSS.register = register; 1103 | /** 1104 | * Get an instance of an object registered with the given id 1105 | * 1106 | * @param instanceId unique id of the registered object 1107 | * @param contextData Optional context data to pass to the contructor of an object factory method 1108 | */ 1109 | function getRegisteredObject(instanceId, contextData) { 1110 | return parentChannel.getObjectRegistry().getInstance(instanceId, contextData); 1111 | } 1112 | VSS.getRegisteredObject = getRegisteredObject; 1113 | /** 1114 | * Fetch an access token which will allow calls to be made to other VSTS services 1115 | */ 1116 | function getAccessToken() { 1117 | return parentChannel.invokeRemoteMethod("getAccessToken", "VSS.HostControl"); 1118 | } 1119 | VSS.getAccessToken = getAccessToken; 1120 | /** 1121 | * Fetch an token which can be used to identify the current user 1122 | */ 1123 | function getAppToken() { 1124 | return parentChannel.invokeRemoteMethod("getAppToken", "VSS.HostControl"); 1125 | } 1126 | VSS.getAppToken = getAppToken; 1127 | /** 1128 | * Requests the parent window to resize the container for this extension based on the current extension size. 1129 | */ 1130 | function resize() { 1131 | if (!bodyElement) { 1132 | bodyElement = document.getElementsByTagName("body").item(0); 1133 | } 1134 | parentChannel.invokeRemoteMethod("resize", "VSS.HostControl", [bodyElement.scrollWidth, bodyElement.scrollHeight]); 1135 | } 1136 | VSS.resize = resize; 1137 | function setupAmdLoader() { 1138 | var hostRootUri = getRootUri(hostPageContext.webContext); 1139 | // Place context so that VSS scripts pick it up correctly 1140 | window.__vssPageContext = hostPageContext; 1141 | // MS Ajax config needs to exist before loading MS Ajax library 1142 | window.__cultureInfo = hostPageContext.microsoftAjaxConfig.cultureInfo; 1143 | // Append CSS first 1144 | if (usingPlatformStyles !== false) { 1145 | if (hostPageContext.coreReferences.stylesheets) { 1146 | hostPageContext.coreReferences.stylesheets.forEach(function (stylesheet) { 1147 | if (stylesheet.isCoreStylesheet) { 1148 | var cssLink = document.createElement("link"); 1149 | cssLink.href = getAbsoluteUrl(stylesheet.url, hostRootUri); 1150 | cssLink.rel = "stylesheet"; 1151 | safeAppendToDom(cssLink, "head"); 1152 | } 1153 | }); 1154 | } 1155 | } 1156 | if (!usingPlatformScripts) { 1157 | // Just wanted to load CSS, no scripts. Can exit here. 1158 | loaderConfigured = true; 1159 | triggerReady(); 1160 | return; 1161 | } 1162 | var scripts = []; 1163 | var anyCoreScriptLoaded = false; 1164 | // Add scripts and loader configuration 1165 | if (hostPageContext.coreReferences.scripts) { 1166 | hostPageContext.coreReferences.scripts.forEach(function (script) { 1167 | if (script.isCoreModule) { 1168 | var alreadyLoaded = false; 1169 | var global = window; 1170 | if (script.identifier === "JQuery") { 1171 | alreadyLoaded = !!global.jQuery; 1172 | } 1173 | else if (script.identifier === "JQueryUI") { 1174 | alreadyLoaded = !!(global.jQuery && global.jQuery.ui && global.jQuery.ui.version); 1175 | } 1176 | else if (script.identifier === "AMDLoader") { 1177 | alreadyLoaded = typeof global.define === "function" && !!global.define.amd; 1178 | } 1179 | if (!alreadyLoaded) { 1180 | scripts.push({ source: getAbsoluteUrl(script.url, hostRootUri) }); 1181 | } 1182 | else { 1183 | anyCoreScriptLoaded = true; 1184 | } 1185 | } 1186 | }); 1187 | if (hostPageContext.coreReferences.coreScriptsBundle && !anyCoreScriptLoaded) { 1188 | // If core scripts bundle exists and no core scripts already loaded by extension, 1189 | // we are free to add core bundle. otherwise, load core scripts individually. 1190 | scripts = [{ source: getAbsoluteUrl(hostPageContext.coreReferences.coreScriptsBundle.url, hostRootUri) }]; 1191 | } 1192 | if (hostPageContext.coreReferences.extensionCoreReferences) { 1193 | scripts.push({ source: getAbsoluteUrl(hostPageContext.coreReferences.extensionCoreReferences.url, hostRootUri) }); 1194 | } 1195 | } 1196 | // Define a new config for extension loader 1197 | var newConfig = { 1198 | baseUrl: extensionContext.baseUri, 1199 | contributionPaths: null, 1200 | paths: {}, 1201 | shim: {} 1202 | }; 1203 | // See whether any configuration specified initially. If yes, copy them to new config 1204 | if (initOptions.moduleLoaderConfig) { 1205 | if (initOptions.moduleLoaderConfig.baseUrl) { 1206 | newConfig.baseUrl = initOptions.moduleLoaderConfig.baseUrl; 1207 | } 1208 | // Copy paths 1209 | extendLoaderPaths(initOptions.moduleLoaderConfig, newConfig); 1210 | // Copy shim 1211 | extendLoaderShim(initOptions.moduleLoaderConfig, newConfig); 1212 | } 1213 | // Use some of the host config to support VSSF and TFS platform as well as some 3rd party libraries 1214 | if (hostPageContext.moduleLoaderConfig) { 1215 | // Copy host shim 1216 | extendLoaderShim(hostPageContext.moduleLoaderConfig, newConfig); 1217 | // Add contribution paths to new config 1218 | var contributionPaths = hostPageContext.moduleLoaderConfig.contributionPaths; 1219 | if (contributionPaths) { 1220 | for (var p in contributionPaths) { 1221 | if (contributionPaths.hasOwnProperty(p) && !newConfig.paths[p]) { 1222 | // Add the contribution path 1223 | var contributionPathValue = contributionPaths[p].value; 1224 | if (!contributionPathValue.match("^https?://")) { 1225 | newConfig.paths[p] = hostRootUri + contributionPathValue; 1226 | } 1227 | else { 1228 | newConfig.paths[p] = contributionPathValue; 1229 | } 1230 | // Look for other path mappings that fall under the contribution path (e.g. "bundles") 1231 | var configPaths = hostPageContext.moduleLoaderConfig.paths; 1232 | if (configPaths) { 1233 | var contributionRoot = p + "/"; 1234 | var rootScriptPath = combinePaths(hostRootUri, hostPageContext.moduleLoaderConfig.baseUrl); 1235 | for (var pathKey in configPaths) { 1236 | if (startsWith(pathKey, contributionRoot)) { 1237 | var pathValue = configPaths[pathKey]; 1238 | if (!pathValue.match("^https?://")) { 1239 | if (pathValue[0] === "/") { 1240 | pathValue = combinePaths(hostRootUri, pathValue); 1241 | } 1242 | else { 1243 | pathValue = combinePaths(rootScriptPath, pathValue); 1244 | } 1245 | } 1246 | newConfig.paths[pathKey] = pathValue; 1247 | } 1248 | } 1249 | } 1250 | } 1251 | } 1252 | } 1253 | } 1254 | // requireJS public api doesn't support reading the current config, so save it off for use by our internal host control. 1255 | window.__vssModuleLoaderConfig = newConfig; 1256 | scripts.push({ content: "require.config(" + JSON.stringify(newConfig) + ");" }); 1257 | addScriptElements(scripts, 0, function () { 1258 | loaderConfigured = true; 1259 | triggerReady(); 1260 | }); 1261 | } 1262 | function startsWith(rootString, startSubstring) { 1263 | if (rootString && rootString.length >= startSubstring.length) { 1264 | return rootString.substr(0, startSubstring.length).localeCompare(startSubstring) === 0; 1265 | } 1266 | return false; 1267 | } 1268 | function combinePaths(path1, path2) { 1269 | var result = path1 || ""; 1270 | if (result[result.length - 1] !== "/") { 1271 | result += "/"; 1272 | } 1273 | if (path2) { 1274 | if (path2[0] === "/") { 1275 | result += path2.substr(1); 1276 | } 1277 | else { 1278 | result += path2; 1279 | } 1280 | } 1281 | return result; 1282 | } 1283 | function extendLoaderPaths(source, target, pathTranslator) { 1284 | if (source.paths) { 1285 | if (!target.paths) { 1286 | target.paths = {}; 1287 | } 1288 | for (var key in source.paths) { 1289 | if (source.paths.hasOwnProperty(key)) { 1290 | var value = source.paths[key]; 1291 | if (pathTranslator) { 1292 | value = pathTranslator(key, source.paths[key]); 1293 | } 1294 | if (value) { 1295 | target.paths[key] = value; 1296 | } 1297 | } 1298 | } 1299 | } 1300 | } 1301 | function extendLoaderShim(source, target) { 1302 | if (source.shim) { 1303 | if (!target.shim) { 1304 | target.shim = {}; 1305 | } 1306 | for (var key in source.shim) { 1307 | if (source.shim.hasOwnProperty(key)) { 1308 | target.shim[key] = source.shim[key]; 1309 | } 1310 | } 1311 | } 1312 | } 1313 | function getRootUri(webContext) { 1314 | var hostContext = (webContext.account || webContext.host); 1315 | var rootUri = hostContext.uri; 1316 | var relativeUri = hostContext.relativeUri; 1317 | if (rootUri && relativeUri) { 1318 | // Ensure both relative and root paths end with a trailing slash before trimming the relative path. 1319 | if (rootUri[rootUri.length - 1] !== "/") { 1320 | rootUri += "/"; 1321 | } 1322 | if (relativeUri[relativeUri.length - 1] !== "/") { 1323 | relativeUri += "/"; 1324 | } 1325 | rootUri = rootUri.substr(0, rootUri.length - relativeUri.length); 1326 | } 1327 | return rootUri; 1328 | } 1329 | function addScriptElements(scripts, index, callback) { 1330 | var _this = this; 1331 | if (index >= scripts.length) { 1332 | callback.call(this); 1333 | return; 1334 | } 1335 | var scriptTag = document.createElement("script"); 1336 | scriptTag.type = "text/javascript"; 1337 | if (scripts[index].source) { 1338 | var scriptSource = scripts[index].source; 1339 | scriptTag.src = scriptSource; 1340 | scriptTag.addEventListener("load", function () { 1341 | addScriptElements.call(_this, scripts, index + 1, callback); 1342 | }); 1343 | scriptTag.addEventListener("error", function (e) { 1344 | notifyLoadFailed("Failed to load script: " + scriptSource); 1345 | }); 1346 | safeAppendToDom(scriptTag, "head"); 1347 | } 1348 | else if (scripts[index].content) { 1349 | scriptTag.textContent = scripts[index].content; 1350 | safeAppendToDom(scriptTag, "head"); 1351 | addScriptElements.call(this, scripts, index + 1, callback); 1352 | } 1353 | } 1354 | function safeAppendToDom(element, section) { 1355 | var parent = document.getElementsByTagName(section)[0]; 1356 | if (!parent) { 1357 | parent = document.createElement(section); 1358 | document.appendChild(parent); 1359 | } 1360 | parent.appendChild(element); 1361 | } 1362 | function getAbsoluteUrl(url, baseUrl) { 1363 | var lcUrl = (url || "").toLowerCase(); 1364 | if (lcUrl.substr(0, 2) !== "//" && lcUrl.substr(0, 5) !== "http:" && lcUrl.substr(0, 6) !== "https:") { 1365 | url = baseUrl + (lcUrl[0] === "/" ? "" : "/") + url; 1366 | } 1367 | return url; 1368 | } 1369 | function triggerReady() { 1370 | var _this = this; 1371 | isReady = true; 1372 | if (readyCallbacks) { 1373 | var savedReadyCallbacks = readyCallbacks; 1374 | readyCallbacks = null; 1375 | savedReadyCallbacks.forEach(function (callback) { 1376 | callback.call(_this); 1377 | }); 1378 | } 1379 | } 1380 | })(VSS || (VSS = {})); 1381 | -------------------------------------------------------------------------------- /src/Vso.StateModelVisualization/scripts/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,i)}):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(){o||(o=document.getElementsByTagName("body").item(0));i.invokeRemoteMethod("resize","VSS.HostControl",[o.scrollWidth,o.scrollHeight])}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="2.2";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