├── .gitattributes ├── .github ├── CODEOWNERS ├── FUNDING.yml └── renovate.json ├── .gitignore ├── LICENSE.md ├── PRIVACY.md ├── README.md ├── ServerTaskExpressionTester.sln ├── ServerTaskExpressionTester ├── App.config ├── CustomResponseNode.cs ├── DictionaryNode.cs ├── Form1.Designer.cs ├── Form1.cs ├── Form1.resx ├── ObjectNode.cs ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings1.Designer.cs │ └── Settings1.settings ├── ServerTaskExpressionTester.csproj └── TextBoxTraceWriter.cs ├── azure-pipelines.yml ├── extension-icon.png ├── extension-icon.psd ├── overview.md ├── screenshot.png ├── vss-extension.json └── what-here.png /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jessehouwing 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: jessehouwing 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: https://www.paypal.me/jessehouwing/5 13 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>jessehouwing/.github:renovate-config" 5 | ], 6 | "azure-pipelines": { 7 | "enabled": true 8 | }, 9 | "packageRules": [ 10 | { 11 | "packageNames": [ 12 | "azure-pipelines-task-lib", 13 | "@types/node" 14 | ], 15 | "updateTypes": ["major"], 16 | "enabled": false 17 | }, 18 | { 19 | "matchDatasources": ["azure-pipelines-tasks"], 20 | "extractVersion": "^(?\\d+)" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.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 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Jesse Houwing 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /PRIVACY.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-gate-condition-editor/9c8f0a8b5176deea676f0d9f4e2639fdd74fb2ff/PRIVACY.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Azure Pipelines Gate Condition Editor 2 | 3 | Ever wondered what to put in the Success Criteria field? 4 | 5 | ![What to put here?](./what-here.png?raw=true "Screenshot") 6 | 7 | Or queued 182 releases to debug the Success Criteria Condition? This little tool will help you locally evaluate your condition syntax in seconds instead of minutes or hours. 8 | 9 | ![Screenshot](./screenshot.png?raw=true "Screenshot") 10 | 11 | # Learn more 12 | 13 | [I wrote this little utility while debugging a condition for one of my own extensions and blogged about the experience](https://jessehouwing.net/vsts-release-create-complex-release-gate/). 14 | 15 | # Prerequisites 16 | 17 | To run you must have following prerequisites installed: 18 | 19 | * [Azure DevOps Server 2019 or later](https://visualstudio.microsoft.com/downloads/) 20 | 21 | It suffices to simpy install Azure DevOps Server on your workstation (Windows 10 x64). You do not need to configure Azure DevOps Server after installation, you won't need IIS to be enabled or SQL Server to be installed either. The installation is used to load the parser and functions. Since the assemblies required cannot be redistributed, this is the easiest way to ensure the utility can load the required dependencies. 22 | -------------------------------------------------------------------------------- /ServerTaskExpressionTester.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29403.142 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServerTaskExpressionTester", "ServerTaskExpressionTester\ServerTaskExpressionTester.csproj", "{E4928F2B-8F53-4556-B419-BD33391F1276}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{200B7374-38C2-4D3E-BE9D-19112CB72A7C}" 9 | ProjectSection(SolutionItems) = preProject 10 | extension-icon.png = extension-icon.png 11 | LICENSE.md = LICENSE.md 12 | overview.md = overview.md 13 | PRIVACY.md = PRIVACY.md 14 | README.md = README.md 15 | screenshot.png = screenshot.png 16 | vss-extension.json = vss-extension.json 17 | what-here.png = what-here.png 18 | EndProjectSection 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {E4928F2B-8F53-4556-B419-BD33391F1276}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {E4928F2B-8F53-4556-B419-BD33391F1276}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {E4928F2B-8F53-4556-B419-BD33391F1276}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {E4928F2B-8F53-4556-B419-BD33391F1276}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {629038DB-F74A-4442-96E9-22F8C60208BC} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /ServerTaskExpressionTester/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /ServerTaskExpressionTester/CustomResponseNode.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.TeamFoundation.DistributedTask.Expressions; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace ServerTaskExpressionTester 10 | { 11 | internal class CustomResponseNode : NamedValueNode, INamedValueInfo 12 | { 13 | private readonly JToken content; 14 | private readonly string statuscode; 15 | private readonly IDictionary headers; 16 | private string name; 17 | 18 | internal CustomResponseNode(string name, JToken content, string statuscode, IDictionary headers) 19 | { 20 | this.content = content; 21 | this.statuscode = statuscode; 22 | this.headers = headers; 23 | this.name = name; 24 | } 25 | 26 | string INamedValueInfo.Name 27 | { 28 | get { return name; } 29 | } 30 | 31 | public NamedValueNode CreateNode() 32 | { 33 | return this; 34 | } 35 | 36 | protected override sealed object EvaluateCore(EvaluationContext evaluationContext) 37 | { 38 | Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); 39 | dictionary["content"] = content; 40 | dictionary["statuscode"] = statuscode; 41 | dictionary["headers"] = headers; 42 | return dictionary; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ServerTaskExpressionTester/DictionaryNode.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.TeamFoundation.DistributedTask.Expressions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace ServerTaskExpressionTester 9 | { 10 | internal class DictionaryNode : NamedValueNode, INamedValueInfo 11 | { 12 | private readonly IDictionary values; 13 | private string name; 14 | 15 | internal DictionaryNode(string name, IDictionary values) 16 | { 17 | this.name = name; 18 | this.Name = name; 19 | this.values = values; 20 | } 21 | 22 | string INamedValueInfo.Name 23 | { 24 | get { return name; } 25 | } 26 | 27 | public NamedValueNode CreateNode() 28 | { 29 | return this; 30 | } 31 | 32 | protected override object EvaluateCore(EvaluationContext context) 33 | { 34 | return values; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ServerTaskExpressionTester/Form1.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace ServerTaskExpressionTester 2 | { 3 | partial class Form1 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.responseEditor = new System.Windows.Forms.TextBox(); 32 | this.expressionEditor = new System.Windows.Forms.TextBox(); 33 | this.dataGridView1 = new System.Windows.Forms.DataGridView(); 34 | this.Key = new System.Windows.Forms.DataGridViewTextBoxColumn(); 35 | this.Value = new System.Windows.Forms.DataGridViewTextBoxColumn(); 36 | this.Statusbar = new System.Windows.Forms.StatusStrip(); 37 | this.Status = new System.Windows.Forms.ToolStripStatusLabel(); 38 | this.Llog = new System.Windows.Forms.TextBox(); 39 | this.splitter1 = new System.Windows.Forms.Splitter(); 40 | this.splitter2 = new System.Windows.Forms.Splitter(); 41 | this.splitter3 = new System.Windows.Forms.Splitter(); 42 | this.dataGridView2 = new System.Windows.Forms.DataGridView(); 43 | this.dataGridViewTextBoxColumn1 = new System.Windows.Forms.DataGridViewTextBoxColumn(); 44 | this.dataGridViewTextBoxColumn2 = new System.Windows.Forms.DataGridViewTextBoxColumn(); 45 | this.label3 = new System.Windows.Forms.Label(); 46 | this.tabControl1 = new System.Windows.Forms.TabControl(); 47 | this.tabPage1 = new System.Windows.Forms.TabPage(); 48 | this.tabPage2 = new System.Windows.Forms.TabPage(); 49 | this.tabPage3 = new System.Windows.Forms.TabPage(); 50 | this.tabPage4 = new System.Windows.Forms.TabPage(); 51 | this.dataGridView3 = new System.Windows.Forms.DataGridView(); 52 | this.dataGridViewTextBoxColumn3 = new System.Windows.Forms.DataGridViewTextBoxColumn(); 53 | this.dataGridViewTextBoxColumn4 = new System.Windows.Forms.DataGridViewTextBoxColumn(); 54 | this.panel1 = new System.Windows.Forms.Panel(); 55 | this.label2 = new System.Windows.Forms.Label(); 56 | this.label1 = new System.Windows.Forms.Label(); 57 | this.textBox1 = new System.Windows.Forms.TextBox(); 58 | ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit(); 59 | this.Statusbar.SuspendLayout(); 60 | ((System.ComponentModel.ISupportInitialize)(this.dataGridView2)).BeginInit(); 61 | this.tabControl1.SuspendLayout(); 62 | this.tabPage1.SuspendLayout(); 63 | this.tabPage2.SuspendLayout(); 64 | this.tabPage3.SuspendLayout(); 65 | this.tabPage4.SuspendLayout(); 66 | ((System.ComponentModel.ISupportInitialize)(this.dataGridView3)).BeginInit(); 67 | this.panel1.SuspendLayout(); 68 | this.SuspendLayout(); 69 | // 70 | // responseEditor 71 | // 72 | this.responseEditor.Dock = System.Windows.Forms.DockStyle.Fill; 73 | this.responseEditor.Font = new System.Drawing.Font("Consolas", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 74 | this.responseEditor.Location = new System.Drawing.Point(3, 3); 75 | this.responseEditor.Multiline = true; 76 | this.responseEditor.Name = "responseEditor"; 77 | this.responseEditor.ScrollBars = System.Windows.Forms.ScrollBars.Both; 78 | this.responseEditor.Size = new System.Drawing.Size(733, 170); 79 | this.responseEditor.TabIndex = 0; 80 | this.responseEditor.TextChanged += new System.EventHandler(this.textBox1_TextChanged); 81 | // 82 | // expressionEditor 83 | // 84 | this.expressionEditor.Dock = System.Windows.Forms.DockStyle.Fill; 85 | this.expressionEditor.Font = new System.Drawing.Font("Consolas", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 86 | this.expressionEditor.Location = new System.Drawing.Point(0, 13); 87 | this.expressionEditor.Multiline = true; 88 | this.expressionEditor.Name = "expressionEditor"; 89 | this.expressionEditor.ScrollBars = System.Windows.Forms.ScrollBars.Both; 90 | this.expressionEditor.Size = new System.Drawing.Size(746, 357); 91 | this.expressionEditor.TabIndex = 2; 92 | this.expressionEditor.TextChanged += new System.EventHandler(this.textBox2_TextChanged); 93 | // 94 | // dataGridView1 95 | // 96 | this.dataGridView1.AllowUserToAddRows = false; 97 | this.dataGridView1.AllowUserToDeleteRows = false; 98 | this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; 99 | this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { 100 | this.Key, 101 | this.Value}); 102 | this.dataGridView1.Dock = System.Windows.Forms.DockStyle.Fill; 103 | this.dataGridView1.Location = new System.Drawing.Point(3, 3); 104 | this.dataGridView1.Name = "dataGridView1"; 105 | this.dataGridView1.RowHeadersWidth = 102; 106 | this.dataGridView1.Size = new System.Drawing.Size(733, 170); 107 | this.dataGridView1.TabIndex = 3; 108 | this.dataGridView1.CellValueChanged += new System.Windows.Forms.DataGridViewCellEventHandler(this.dataGridView1_CellValueChanged); 109 | // 110 | // Key 111 | // 112 | this.Key.HeaderText = "Key"; 113 | this.Key.MinimumWidth = 12; 114 | this.Key.Name = "Key"; 115 | this.Key.ReadOnly = true; 116 | this.Key.Width = 250; 117 | // 118 | // Value 119 | // 120 | this.Value.HeaderText = "Value"; 121 | this.Value.MinimumWidth = 12; 122 | this.Value.Name = "Value"; 123 | this.Value.Width = 250; 124 | // 125 | // Statusbar 126 | // 127 | this.Statusbar.ImageScalingSize = new System.Drawing.Size(36, 36); 128 | this.Statusbar.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { 129 | this.Status}); 130 | this.Statusbar.Location = new System.Drawing.Point(0, 574); 131 | this.Statusbar.Name = "Statusbar"; 132 | this.Statusbar.Padding = new System.Windows.Forms.Padding(0, 0, 6, 0); 133 | this.Statusbar.Size = new System.Drawing.Size(1024, 22); 134 | this.Statusbar.TabIndex = 4; 135 | this.Statusbar.Text = "dfghdfgh"; 136 | // 137 | // Status 138 | // 139 | this.Status.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; 140 | this.Status.DoubleClickEnabled = true; 141 | this.Status.Name = "Status"; 142 | this.Status.Size = new System.Drawing.Size(59, 17); 143 | this.Status.Text = "Loading..."; 144 | this.Status.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; 145 | this.Status.DoubleClick += new System.EventHandler(this.LLog_DoubleClick); 146 | // 147 | // Llog 148 | // 149 | this.Llog.Dock = System.Windows.Forms.DockStyle.Right; 150 | this.Llog.Location = new System.Drawing.Point(747, 0); 151 | this.Llog.Margin = new System.Windows.Forms.Padding(1); 152 | this.Llog.Multiline = true; 153 | this.Llog.Name = "Llog"; 154 | this.Llog.ReadOnly = true; 155 | this.Llog.ScrollBars = System.Windows.Forms.ScrollBars.Both; 156 | this.Llog.Size = new System.Drawing.Size(277, 574); 157 | this.Llog.TabIndex = 5; 158 | // 159 | // splitter1 160 | // 161 | this.splitter1.Dock = System.Windows.Forms.DockStyle.Right; 162 | this.splitter1.Location = new System.Drawing.Point(746, 0); 163 | this.splitter1.Margin = new System.Windows.Forms.Padding(1); 164 | this.splitter1.Name = "splitter1"; 165 | this.splitter1.Size = new System.Drawing.Size(1, 372); 166 | this.splitter1.TabIndex = 6; 167 | this.splitter1.TabStop = false; 168 | // 169 | // splitter2 170 | // 171 | this.splitter2.Dock = System.Windows.Forms.DockStyle.Bottom; 172 | this.splitter2.Location = new System.Drawing.Point(0, 370); 173 | this.splitter2.Margin = new System.Windows.Forms.Padding(1); 174 | this.splitter2.Name = "splitter2"; 175 | this.splitter2.Size = new System.Drawing.Size(746, 1); 176 | this.splitter2.TabIndex = 7; 177 | this.splitter2.TabStop = false; 178 | // 179 | // splitter3 180 | // 181 | this.splitter3.Dock = System.Windows.Forms.DockStyle.Bottom; 182 | this.splitter3.Location = new System.Drawing.Point(0, 371); 183 | this.splitter3.Margin = new System.Windows.Forms.Padding(1); 184 | this.splitter3.Name = "splitter3"; 185 | this.splitter3.Size = new System.Drawing.Size(746, 1); 186 | this.splitter3.TabIndex = 8; 187 | this.splitter3.TabStop = false; 188 | // 189 | // dataGridView2 190 | // 191 | this.dataGridView2.AllowUserToAddRows = false; 192 | this.dataGridView2.AllowUserToDeleteRows = false; 193 | this.dataGridView2.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; 194 | this.dataGridView2.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { 195 | this.dataGridViewTextBoxColumn1, 196 | this.dataGridViewTextBoxColumn2}); 197 | this.dataGridView2.Dock = System.Windows.Forms.DockStyle.Fill; 198 | this.dataGridView2.Location = new System.Drawing.Point(3, 3); 199 | this.dataGridView2.Name = "dataGridView2"; 200 | this.dataGridView2.RowHeadersWidth = 102; 201 | this.dataGridView2.Size = new System.Drawing.Size(733, 170); 202 | this.dataGridView2.TabIndex = 10; 203 | this.dataGridView2.CellValueChanged += new System.Windows.Forms.DataGridViewCellEventHandler(this.dataGridView1_CellValueChanged); 204 | // 205 | // dataGridViewTextBoxColumn1 206 | // 207 | this.dataGridViewTextBoxColumn1.HeaderText = "Key"; 208 | this.dataGridViewTextBoxColumn1.MinimumWidth = 12; 209 | this.dataGridViewTextBoxColumn1.Name = "dataGridViewTextBoxColumn1"; 210 | this.dataGridViewTextBoxColumn1.ReadOnly = true; 211 | this.dataGridViewTextBoxColumn1.Width = 250; 212 | // 213 | // dataGridViewTextBoxColumn2 214 | // 215 | this.dataGridViewTextBoxColumn2.HeaderText = "Value"; 216 | this.dataGridViewTextBoxColumn2.MinimumWidth = 12; 217 | this.dataGridViewTextBoxColumn2.Name = "dataGridViewTextBoxColumn2"; 218 | this.dataGridViewTextBoxColumn2.Width = 250; 219 | // 220 | // label3 221 | // 222 | this.label3.AutoSize = true; 223 | this.label3.Dock = System.Windows.Forms.DockStyle.Top; 224 | this.label3.Location = new System.Drawing.Point(0, 0); 225 | this.label3.Name = "label3"; 226 | this.label3.Size = new System.Drawing.Size(102, 13); 227 | this.label3.TabIndex = 12; 228 | this.label3.Text = "Success Expression"; 229 | // 230 | // tabControl1 231 | // 232 | this.tabControl1.Controls.Add(this.tabPage1); 233 | this.tabControl1.Controls.Add(this.tabPage2); 234 | this.tabControl1.Controls.Add(this.tabPage3); 235 | this.tabControl1.Controls.Add(this.tabPage4); 236 | this.tabControl1.Dock = System.Windows.Forms.DockStyle.Bottom; 237 | this.tabControl1.Location = new System.Drawing.Point(0, 372); 238 | this.tabControl1.Name = "tabControl1"; 239 | this.tabControl1.SelectedIndex = 0; 240 | this.tabControl1.Size = new System.Drawing.Size(747, 202); 241 | this.tabControl1.TabIndex = 14; 242 | // 243 | // tabPage1 244 | // 245 | this.tabPage1.Controls.Add(this.responseEditor); 246 | this.tabPage1.Location = new System.Drawing.Point(4, 22); 247 | this.tabPage1.Name = "tabPage1"; 248 | this.tabPage1.Padding = new System.Windows.Forms.Padding(3); 249 | this.tabPage1.Size = new System.Drawing.Size(739, 176); 250 | this.tabPage1.TabIndex = 0; 251 | this.tabPage1.Text = "Content"; 252 | this.tabPage1.UseVisualStyleBackColor = true; 253 | // 254 | // tabPage2 255 | // 256 | this.tabPage2.Controls.Add(this.dataGridView1); 257 | this.tabPage2.Location = new System.Drawing.Point(4, 22); 258 | this.tabPage2.Name = "tabPage2"; 259 | this.tabPage2.Padding = new System.Windows.Forms.Padding(3); 260 | this.tabPage2.Size = new System.Drawing.Size(739, 176); 261 | this.tabPage2.TabIndex = 1; 262 | this.tabPage2.Text = "Variables"; 263 | this.tabPage2.UseVisualStyleBackColor = true; 264 | // 265 | // tabPage3 266 | // 267 | this.tabPage3.Controls.Add(this.dataGridView2); 268 | this.tabPage3.Location = new System.Drawing.Point(4, 22); 269 | this.tabPage3.Name = "tabPage3"; 270 | this.tabPage3.Padding = new System.Windows.Forms.Padding(3); 271 | this.tabPage3.Size = new System.Drawing.Size(739, 176); 272 | this.tabPage3.TabIndex = 2; 273 | this.tabPage3.Text = "Task Inputs (2019+)"; 274 | this.tabPage3.UseVisualStyleBackColor = true; 275 | // 276 | // tabPage4 277 | // 278 | this.tabPage4.Controls.Add(this.dataGridView3); 279 | this.tabPage4.Controls.Add(this.panel1); 280 | this.tabPage4.Location = new System.Drawing.Point(4, 22); 281 | this.tabPage4.Name = "tabPage4"; 282 | this.tabPage4.Padding = new System.Windows.Forms.Padding(3); 283 | this.tabPage4.Size = new System.Drawing.Size(739, 176); 284 | this.tabPage4.TabIndex = 3; 285 | this.tabPage4.Text = "Response (2019+)"; 286 | this.tabPage4.UseVisualStyleBackColor = true; 287 | // 288 | // dataGridView3 289 | // 290 | this.dataGridView3.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; 291 | this.dataGridView3.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { 292 | this.dataGridViewTextBoxColumn3, 293 | this.dataGridViewTextBoxColumn4}); 294 | this.dataGridView3.Dock = System.Windows.Forms.DockStyle.Fill; 295 | this.dataGridView3.Location = new System.Drawing.Point(3, 68); 296 | this.dataGridView3.Name = "dataGridView3"; 297 | this.dataGridView3.RowHeadersWidth = 102; 298 | this.dataGridView3.Size = new System.Drawing.Size(733, 105); 299 | this.dataGridView3.TabIndex = 11; 300 | this.dataGridView3.CellValueChanged += new System.Windows.Forms.DataGridViewCellEventHandler(this.dataGridView1_CellValueChanged); 301 | // 302 | // dataGridViewTextBoxColumn3 303 | // 304 | this.dataGridViewTextBoxColumn3.HeaderText = "Key"; 305 | this.dataGridViewTextBoxColumn3.MinimumWidth = 12; 306 | this.dataGridViewTextBoxColumn3.Name = "dataGridViewTextBoxColumn3"; 307 | this.dataGridViewTextBoxColumn3.Width = 250; 308 | // 309 | // dataGridViewTextBoxColumn4 310 | // 311 | this.dataGridViewTextBoxColumn4.HeaderText = "Value"; 312 | this.dataGridViewTextBoxColumn4.MinimumWidth = 12; 313 | this.dataGridViewTextBoxColumn4.Name = "dataGridViewTextBoxColumn4"; 314 | this.dataGridViewTextBoxColumn4.Width = 250; 315 | // 316 | // panel1 317 | // 318 | this.panel1.Controls.Add(this.label2); 319 | this.panel1.Controls.Add(this.label1); 320 | this.panel1.Controls.Add(this.textBox1); 321 | this.panel1.Dock = System.Windows.Forms.DockStyle.Top; 322 | this.panel1.Location = new System.Drawing.Point(3, 3); 323 | this.panel1.Name = "panel1"; 324 | this.panel1.Size = new System.Drawing.Size(733, 65); 325 | this.panel1.TabIndex = 12; 326 | // 327 | // label2 328 | // 329 | this.label2.AutoSize = true; 330 | this.label2.Location = new System.Drawing.Point(5, 42); 331 | this.label2.Name = "label2"; 332 | this.label2.Size = new System.Drawing.Size(47, 13); 333 | this.label2.TabIndex = 3; 334 | this.label2.Text = "Headers"; 335 | // 336 | // label1 337 | // 338 | this.label1.AutoSize = true; 339 | this.label1.Location = new System.Drawing.Point(5, 16); 340 | this.label1.Name = "label1"; 341 | this.label1.Size = new System.Drawing.Size(61, 13); 342 | this.label1.TabIndex = 2; 343 | this.label1.Text = "Statuscode"; 344 | // 345 | // textBox1 346 | // 347 | this.textBox1.Location = new System.Drawing.Point(99, 9); 348 | this.textBox1.Name = "textBox1"; 349 | this.textBox1.Size = new System.Drawing.Size(100, 20); 350 | this.textBox1.TabIndex = 0; 351 | this.textBox1.Text = "200"; 352 | this.textBox1.TextChanged += new System.EventHandler(this.textBox1_TextChanged_1); 353 | // 354 | // Form1 355 | // 356 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 357 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 358 | this.ClientSize = new System.Drawing.Size(1024, 596); 359 | this.Controls.Add(this.expressionEditor); 360 | this.Controls.Add(this.label3); 361 | this.Controls.Add(this.splitter2); 362 | this.Controls.Add(this.splitter3); 363 | this.Controls.Add(this.splitter1); 364 | this.Controls.Add(this.tabControl1); 365 | this.Controls.Add(this.Llog); 366 | this.Controls.Add(this.Statusbar); 367 | this.Name = "Form1"; 368 | this.Text = "Gate Condition Editor for Azure Pipelines"; 369 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Form1_FormClosing); 370 | this.Load += new System.EventHandler(this.Form1_Load); 371 | ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit(); 372 | this.Statusbar.ResumeLayout(false); 373 | this.Statusbar.PerformLayout(); 374 | ((System.ComponentModel.ISupportInitialize)(this.dataGridView2)).EndInit(); 375 | this.tabControl1.ResumeLayout(false); 376 | this.tabPage1.ResumeLayout(false); 377 | this.tabPage1.PerformLayout(); 378 | this.tabPage2.ResumeLayout(false); 379 | this.tabPage3.ResumeLayout(false); 380 | this.tabPage4.ResumeLayout(false); 381 | ((System.ComponentModel.ISupportInitialize)(this.dataGridView3)).EndInit(); 382 | this.panel1.ResumeLayout(false); 383 | this.panel1.PerformLayout(); 384 | this.ResumeLayout(false); 385 | this.PerformLayout(); 386 | 387 | } 388 | 389 | #endregion 390 | 391 | private System.Windows.Forms.TextBox responseEditor; 392 | private System.Windows.Forms.TextBox expressionEditor; 393 | private System.Windows.Forms.DataGridView dataGridView1; 394 | private System.Windows.Forms.DataGridViewTextBoxColumn Key; 395 | private System.Windows.Forms.DataGridViewTextBoxColumn Value; 396 | private System.Windows.Forms.StatusStrip Statusbar; 397 | private System.Windows.Forms.ToolStripStatusLabel Status; 398 | private System.Windows.Forms.TextBox Llog; 399 | private System.Windows.Forms.Splitter splitter1; 400 | private System.Windows.Forms.Splitter splitter2; 401 | private System.Windows.Forms.Splitter splitter3; 402 | private System.Windows.Forms.DataGridView dataGridView2; 403 | private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn1; 404 | private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn2; 405 | private System.Windows.Forms.Label label3; 406 | private System.Windows.Forms.TabControl tabControl1; 407 | private System.Windows.Forms.TabPage tabPage1; 408 | private System.Windows.Forms.TabPage tabPage2; 409 | private System.Windows.Forms.TabPage tabPage3; 410 | private System.Windows.Forms.TabPage tabPage4; 411 | private System.Windows.Forms.DataGridView dataGridView3; 412 | private System.Windows.Forms.Panel panel1; 413 | private System.Windows.Forms.Label label2; 414 | private System.Windows.Forms.Label label1; 415 | private System.Windows.Forms.TextBox textBox1; 416 | private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn3; 417 | private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn4; 418 | } 419 | } 420 | 421 | -------------------------------------------------------------------------------- /ServerTaskExpressionTester/Form1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | using System.Windows.Forms; 7 | using Microsoft.TeamFoundation.DistributedTask.Expressions; 8 | using Microsoft.TeamFoundation.DistributedTask.Pipelines.Expressions; 9 | using Newtonsoft.Json; 10 | using Newtonsoft.Json.Linq; 11 | using ServerTaskExpressionTester.Properties; 12 | 13 | namespace ServerTaskExpressionTester 14 | { 15 | public partial class Form1 : Form 16 | { 17 | public Form1() 18 | { 19 | InitializeComponent(); 20 | 21 | expressionEditor.MaxLength = int.MaxValue; 22 | responseEditor.MaxLength = int.MaxValue; 23 | Llog.MaxLength = int.MaxValue; 24 | Status.Text = ""; 25 | } 26 | 27 | private IFunctionInfo InitializeFunctionNode(string name, int minParameters, int maxParameters) 28 | { 29 | Type functionType = Type.GetType("Microsoft.TeamFoundation.DistributedTask.Expressions.FunctionInfo`1[[Microsoft.TeamFoundation.DistributedTask.Orchestration.Server.Extensions.ServerExecutionTasks.HttpRequest." + name + "Node, Microsoft.TeamFoundation.DistributedTask.Orchestration.Server.Extensions]], Microsoft.TeamFoundation.DistributedTask.WebApi"); 30 | return (IFunctionInfo)functionType.GetConstructor(new[]{ 31 | typeof(string), typeof(int), typeof(int) } 32 | ).Invoke(new object[] { name, minParameters, maxParameters }); 33 | } 34 | 35 | private void Evaluate() 36 | { 37 | var text = expressionEditor.Text; 38 | var response = responseEditor.Text; 39 | if (string.IsNullOrWhiteSpace(text) || string.IsNullOrWhiteSpace(response)) 40 | { 41 | return; 42 | } 43 | 44 | IDictionary variables = GridToDictionary(dataGridView1); 45 | foreach (var variable in variables) 46 | { 47 | text = text.Replace("$(" + variable.Key + ")", variable.Value); 48 | } 49 | 50 | try 51 | { 52 | Llog.Text = string.Empty; 53 | 54 | IFunctionInfo[] functionInfoArray = { 55 | InitializeFunctionNode("JsonPath", 1, 1), 56 | InitializeFunctionNode("Count", 1, 1), 57 | InitializeFunctionNode("IsNullOrEmpty", 1, 1), 58 | InitializeFunctionNode("Split", 2, 2), 59 | InitializeFunctionNode("Intersect", 2, 2), 60 | (IFunctionInfo) new FunctionInfo("length", 1, 1), 61 | (IFunctionInfo) new FunctionInfo("isUrl", 1, 1) 62 | }; 63 | 64 | bool result = false; 65 | try 66 | { 67 | JToken jtoken = JToken.Parse(response); 68 | 69 | ExpressionParser parser = new ExpressionParser(); 70 | var tree = parser.CreateTree( 71 | expression: text, 72 | trace: null, 73 | namedValues: new INamedValueInfo[] { 74 | new DictionaryNode("Variables", variables), 75 | new DictionaryNode("TaskInputs", GridToDictionary(dataGridView2)), 76 | new ObjectNode("Root", jtoken), 77 | new CustomResponseNode("Response", jtoken, textBox1.Text, GridToDictionary(dataGridView3)) 78 | }, 79 | functions: functionInfoArray 80 | ); 81 | 82 | result = tree.EvaluateBoolean( 83 | trace: new TextBoxTraceWriter(Llog), 84 | secretMasker: null, 85 | state: jtoken 86 | ); 87 | } 88 | catch (Exception e) 89 | { 90 | Llog.Text += Environment.NewLine + e.Message; 91 | } 92 | 93 | if (result) 94 | { 95 | Status.Text = "Success"; 96 | Status.ForeColor = Color.Aquamarine; 97 | } 98 | else 99 | { 100 | Status.Text = "Failed"; 101 | Status.ForeColor = Color.DarkOrange; 102 | } 103 | } 104 | catch (Exception ex) 105 | { 106 | Status.Text = "Error"; 107 | Status.ForeColor = Color.DarkRed; 108 | Llog.Text = ex.Message; 109 | } 110 | } 111 | 112 | public IList GetVariables() 113 | { 114 | var matches = Regex.Matches(expressionEditor.Text, @"\$\((?[^\)]+)\)|variables\[\'(?[^\]]+)'\]|variables\.(?[a-zA-Z0-9_.-]+)", RegexOptions.IgnoreCase); 115 | return matches.Cast().Select(m => m.Groups["variablename"].Value).ToList(); 116 | } 117 | 118 | public IList GetTaskInputs() 119 | { 120 | var matches = Regex.Matches(expressionEditor.Text, @"taskinputs\[\'(?[^\]]+)'\]|taskinputs\.(?[a-zA-Z0-9_.-]+)", RegexOptions.IgnoreCase); 121 | return matches.Cast().Select(m => m.Groups["variablename"].Value).ToList(); 122 | } 123 | 124 | private void textBox2_TextChanged(object sender, EventArgs e) 125 | { 126 | UpdateGrid(dataGridView1, GetVariables()); 127 | UpdateGrid(dataGridView2, GetTaskInputs()); 128 | 129 | Evaluate(); 130 | } 131 | 132 | private void UpdateGrid(DataGridView grid, IEnumerable variables) 133 | { 134 | var existingVariables = grid.Rows.Cast().Select(row => row.Cells[0].Value).Distinct().ToArray(); 135 | var newVariables = variables.Except(existingVariables).Distinct(); 136 | var deleteVariables = existingVariables.Except(variables).Distinct(); 137 | 138 | var rowsToDelete = grid.Rows.Cast() 139 | .Where(row => deleteVariables.Contains(row.Cells[0].Value)); 140 | 141 | foreach (var row in rowsToDelete) 142 | { 143 | grid.Rows.Remove(row); 144 | } 145 | 146 | foreach (var variable in newVariables) 147 | { 148 | grid.Rows.Add(variable, ""); 149 | } 150 | } 151 | 152 | private void textBox1_TextChanged(object sender, EventArgs e) 153 | { 154 | Evaluate(); 155 | } 156 | 157 | private void Form1_Load(object sender, EventArgs e) 158 | { 159 | var json = Settings1.Default.Variables; 160 | if (!string.IsNullOrWhiteSpace(json)) 161 | { 162 | var variables = JsonConvert.DeserializeObject>(json); 163 | foreach (var variable in variables) 164 | { 165 | dataGridView1.Rows.Add(variable.Key, variable.Value); 166 | } 167 | } 168 | 169 | json = Settings1.Default.TaskInputs; 170 | if (!string.IsNullOrWhiteSpace(json)) 171 | { 172 | var variables = JsonConvert.DeserializeObject>(json); 173 | foreach (var variable in variables) 174 | { 175 | dataGridView2.Rows.Add(variable.Key, variable.Value); 176 | } 177 | } 178 | 179 | json = Settings1.Default.Headers; 180 | if (!string.IsNullOrWhiteSpace(json)) 181 | { 182 | var variables = JsonConvert.DeserializeObject>(json); 183 | foreach (var variable in variables) 184 | { 185 | dataGridView3.Rows.Add(variable.Key, variable.Value); 186 | } 187 | } 188 | 189 | textBox1.Text = Settings1.Default.StatusCode ?? string.Empty; 190 | expressionEditor.Text = Settings1.Default.Expression; 191 | responseEditor.Text = Settings1.Default.JsonBody; 192 | } 193 | 194 | private void Form1_FormClosing(object sender, FormClosingEventArgs e) 195 | { 196 | Settings1.Default.JsonBody = responseEditor.Text; 197 | Settings1.Default.Expression = expressionEditor.Text; 198 | 199 | Settings1.Default.Variables = JsonConvert.SerializeObject(GridToDictionary(dataGridView1)); 200 | Settings1.Default.TaskInputs = JsonConvert.SerializeObject(GridToDictionary(dataGridView2)); 201 | Settings1.Default.StatusCode = textBox1.Text; 202 | Settings1.Default.Headers = JsonConvert.SerializeObject(GridToDictionary(dataGridView3)); 203 | Settings1.Default.Save(); 204 | } 205 | 206 | private Dictionary GridToDictionary(DataGridView grid) 207 | { 208 | var taskInputs = new Dictionary(StringComparer.OrdinalIgnoreCase); 209 | foreach (var row in grid.Rows.Cast()) 210 | { 211 | string key = (string)row.Cells[0].Value; 212 | string value = (string)row.Cells[1].Value; 213 | 214 | if (!string.IsNullOrEmpty(key)) 215 | { 216 | taskInputs.Add(key, value); 217 | } 218 | } 219 | 220 | return taskInputs; 221 | } 222 | 223 | private void LLog_DoubleClick(object sender, EventArgs e) 224 | { 225 | if (!string.IsNullOrWhiteSpace(Llog.Text)) 226 | { 227 | MessageBox.Show(Llog.Text, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); 228 | } 229 | } 230 | 231 | private void dataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e) 232 | { 233 | Evaluate(); 234 | } 235 | 236 | private void textBox1_TextChanged_1(object sender, EventArgs e) 237 | { 238 | Evaluate(); 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /ServerTaskExpressionTester/Form1.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 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 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | True 122 | 123 | 124 | True 125 | 126 | 127 | True 128 | 129 | 130 | True 131 | 132 | 133 | 17, 17 134 | 135 | 136 | True 137 | 138 | 139 | True 140 | 141 | 142 | True 143 | 144 | 145 | True 146 | 147 | 148 | True 149 | 150 | 151 | True 152 | 153 | 154 | True 155 | 156 | 157 | True 158 | 159 | -------------------------------------------------------------------------------- /ServerTaskExpressionTester/ObjectNode.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.TeamFoundation.DistributedTask.Expressions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace ServerTaskExpressionTester 9 | { 10 | internal class ObjectNode : NamedValueNode, INamedValueInfo 11 | { 12 | private readonly object value; 13 | private string name; 14 | 15 | internal ObjectNode(string name, object value) 16 | { 17 | this.name = name; 18 | this.Name = name; 19 | this.value = value; 20 | } 21 | 22 | string INamedValueInfo.Name 23 | { 24 | get { return name; } 25 | } 26 | 27 | public NamedValueNode CreateNode() 28 | { 29 | return this; 30 | } 31 | 32 | protected override object EvaluateCore(EvaluationContext context) 33 | { 34 | return value; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ServerTaskExpressionTester/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Threading.Tasks; 7 | using System.Windows.Forms; 8 | using Microsoft.Win32; 9 | 10 | namespace ServerTaskExpressionTester 11 | { 12 | static class Program 13 | { 14 | static string tfsPath17 = (string)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\TeamFoundationServer\17.0", "InstallPath", string.Empty); 15 | static string tfsPath18 = (string)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\TeamFoundationServer\18.0", "InstallPath", string.Empty); 16 | static string tfsPath19 = (string)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\TeamFoundationServer\19.0", "InstallPath", string.Empty); 17 | static string localpath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); 18 | static string tfsPath = tfsPath17 ?? tfsPath18 ?? tfsPath19; 19 | 20 | /// 21 | /// The main entry point for the application. 22 | /// 23 | [STAThread] 24 | static void Main() 25 | { 26 | AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; 27 | Application.EnableVisualStyles(); 28 | Application.SetCompatibleTextRenderingDefault(false); 29 | Application.Run(new Form1()); 30 | } 31 | 32 | private static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) 33 | { 34 | Dictionary assemblies = new Dictionary 35 | { 36 | ["Microsoft.TeamFoundation.DistributedTask.Orchestration.Server"] = @"Tools\", 37 | ["Microsoft.TeamFoundation.DistributedTask.Orchestration.Server.Extensions"] = @"Tools\Plugins", 38 | ["Microsoft.TeamFoundation.DistributedTask.WebApi"] = @"Tools\", 39 | ["Microsoft.VisualStudio.Services.Common"] = @"Tools\", 40 | ["Microsoft.VisualStudio.Services.WebApi"] = @"Tools\", 41 | ["Microsoft.TeamFoundation.Framework.Server"] = @"Tools\", 42 | ["Newtonsoft.Json"] = @"Tools\" 43 | }; 44 | 45 | AssemblyName name = new AssemblyName(args.Name); 46 | 47 | #if DEBUG 48 | string path = localpath; 49 | string assemblyPath = Path.Combine(localpath, "2020", name.Name + ".dll"); 50 | #else 51 | string path = tfsPath; 52 | string assemblyPath = Path.Combine(tfsPath, assemblies[name.Name], name.Name + ".dll"); 53 | #endif 54 | 55 | if (string.IsNullOrWhiteSpace(path)) 56 | { 57 | MessageBox.Show("Please install Azure DevOps Server 2019 or later before running this tool.", "Cannot load.", 58 | MessageBoxButtons.OK, MessageBoxIcon.Stop); 59 | Application.Exit(); 60 | } 61 | else 62 | { 63 | if (assemblies.Keys.Contains(name.Name)) 64 | { 65 | try 66 | { 67 | var ass = Assembly.LoadFrom(assemblyPath); 68 | return ass; 69 | } 70 | catch (Exception e) 71 | { 72 | Console.Error.WriteLine(e.Message); 73 | return null; 74 | } 75 | } 76 | } 77 | 78 | return null; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /ServerTaskExpressionTester/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("ServerTaskExpressionTester")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ServerTaskExpressionTester")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 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("e4928f2b-8f53-4556-b419-bd33391f1276")] 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 Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /ServerTaskExpressionTester/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace ServerTaskExpressionTester.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ServerTaskExpressionTester.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /ServerTaskExpressionTester/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 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 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /ServerTaskExpressionTester/Properties/Settings1.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace ServerTaskExpressionTester.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.5.0.0")] 16 | internal sealed partial class Settings1 : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings1 defaultInstance = ((Settings1)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings1()))); 19 | 20 | public static Settings1 Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | 26 | [global::System.Configuration.UserScopedSettingAttribute()] 27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 28 | [global::System.Configuration.DefaultSettingValueAttribute("")] 29 | public string Expression { 30 | get { 31 | return ((string)(this["Expression"])); 32 | } 33 | set { 34 | this["Expression"] = value; 35 | } 36 | } 37 | 38 | [global::System.Configuration.UserScopedSettingAttribute()] 39 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 40 | [global::System.Configuration.DefaultSettingValueAttribute("")] 41 | public string JsonBody { 42 | get { 43 | return ((string)(this["JsonBody"])); 44 | } 45 | set { 46 | this["JsonBody"] = value; 47 | } 48 | } 49 | 50 | [global::System.Configuration.UserScopedSettingAttribute()] 51 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 52 | [global::System.Configuration.DefaultSettingValueAttribute("")] 53 | public string Variables { 54 | get { 55 | return ((string)(this["Variables"])); 56 | } 57 | set { 58 | this["Variables"] = value; 59 | } 60 | } 61 | 62 | [global::System.Configuration.UserScopedSettingAttribute()] 63 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 64 | [global::System.Configuration.DefaultSettingValueAttribute("")] 65 | public string TaskInputs { 66 | get { 67 | return ((string)(this["TaskInputs"])); 68 | } 69 | set { 70 | this["TaskInputs"] = value; 71 | } 72 | } 73 | 74 | [global::System.Configuration.UserScopedSettingAttribute()] 75 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 76 | [global::System.Configuration.DefaultSettingValueAttribute("")] 77 | public string StatusCode { 78 | get { 79 | return ((string)(this["StatusCode"])); 80 | } 81 | set { 82 | this["StatusCode"] = value; 83 | } 84 | } 85 | 86 | [global::System.Configuration.UserScopedSettingAttribute()] 87 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 88 | [global::System.Configuration.DefaultSettingValueAttribute("")] 89 | public string Headers { 90 | get { 91 | return ((string)(this["Headers"])); 92 | } 93 | set { 94 | this["Headers"] = value; 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /ServerTaskExpressionTester/Properties/Settings1.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /ServerTaskExpressionTester/ServerTaskExpressionTester.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | false 8 | {E4928F2B-8F53-4556-B419-BD33391F1276} 9 | WinExe 10 | ServerTaskExpressionTester 11 | ServerTaskExpressionTester 12 | v4.8 13 | 512 14 | true 15 | 16 | 17 | 18 | $(Registry:HKEY_LOCAL_MACHINE\Software\Microsoft\TeamFoundationServer\17.0@InstallPath) 19 | $(Registry:HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\TeamFoundationServer\17.0@InstallPath) 20 | $(Registry:HKEY_LOCAL_MACHINE\Software\Microsoft\TeamFoundationServer\18.0@InstallPath) 21 | $(Registry:HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\TeamFoundationServer\18.0@InstallPath) 22 | $(Registry:HKEY_LOCAL_MACHINE\Software\Microsoft\TeamFoundationServer\19.0@InstallPath) 23 | $(Registry:HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\TeamFoundationServer\19.0@InstallPath) 24 | 25 | 26 | AnyCPU 27 | true 28 | full 29 | false 30 | bin\Debug\ 31 | DEBUG;TRACE 32 | prompt 33 | 4 34 | 35 | 36 | AnyCPU 37 | pdbonly 38 | true 39 | bin\Release\ 40 | TRACE 41 | prompt 42 | 4 43 | 44 | 45 | 46 | $(TfsInstallationDirectory)\Tools\Microsoft.TeamFoundation.DistributedTask.Orchestration.Server.dll 47 | False 48 | False 49 | 50 | 51 | $(TfsInstallationDirectory)\Tools\Plugins\Microsoft.TeamFoundation.DistributedTask.Orchestration.Server.Extensions.dll 52 | False 53 | False 54 | 55 | 56 | $(TfsInstallationDirectory)\Application Tier\Message Queue\bin\Microsoft.TeamFoundation.DistributedTask.WebApi.dll 57 | False 58 | False 59 | 60 | 61 | $(TfsInstallationDirectory)\Tools\Microsoft.TeamFoundation.Framework.Server.dll 62 | False 63 | False 64 | 65 | 66 | $(TfsInstallationDirectory)\Tools\Newtonsoft.Json.dll 67 | False 68 | True 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | Form 86 | 87 | 88 | Form1.cs 89 | 90 | 91 | 92 | 93 | True 94 | True 95 | Settings1.settings 96 | 97 | 98 | 99 | 100 | 101 | Form1.cs 102 | 103 | 104 | ResXFileCodeGenerator 105 | Resources.Designer.cs 106 | Designer 107 | 108 | 109 | True 110 | Resources.resx 111 | True 112 | 113 | 114 | SettingsSingleFileGenerator 115 | Settings1.Designer.cs 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /ServerTaskExpressionTester/TextBoxTraceWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows.Forms; 7 | using Microsoft.TeamFoundation.DistributedTask.Expressions; 8 | 9 | namespace ServerTaskExpressionTester 10 | { 11 | internal class TextBoxTraceWriter :ITraceWriter 12 | { 13 | private TextBox target; 14 | 15 | public TextBoxTraceWriter(TextBox target) 16 | { 17 | this.target = target; 18 | } 19 | 20 | public void Info(string message) 21 | { 22 | target.Text += Environment.NewLine + message; 23 | } 24 | 25 | public void Verbose(string message) 26 | { 27 | target.Text += Environment.NewLine + message; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | name: 1.1$(Rev:.r) 2 | 3 | trigger: 4 | - main 5 | 6 | pool: "Azure DevOps Server 2022" 7 | 8 | variables: 9 | solution: '**/*.sln' 10 | buildPlatform: 'Any CPU' 11 | buildConfiguration: 'Release' 12 | vsix: '$(Build.ArtifactStagingDirectory)/vsix/gate-condition-editor-$(Build.BuildNumber).vsix' 13 | 14 | steps: 15 | - task: NuGetToolInstaller@1 16 | 17 | - task: NuGetCommand@2 18 | inputs: 19 | restoreSolution: '$(solution)' 20 | 21 | - task: VSBuild@1 22 | inputs: 23 | solution: '$(solution)' 24 | platform: '$(buildPlatform)' 25 | configuration: '$(buildConfiguration)' 26 | 27 | - task: CopyFiles@2.211.0 28 | inputs: 29 | SourceFolder: '$(Build.SourcesDirectory)/ServerTaskExpressionTester/bin/$(buildConfiguration)' 30 | Contents: | 31 | *.pdb 32 | *.exe 33 | TargetFolder: '$(Build.ArtifactStagingDirectory)' 34 | 35 | - task: NodeTool@0.213.0 36 | inputs: 37 | versionSpec: '10.x' 38 | checkLatest: true 39 | 40 | - task: TfxInstaller@5 41 | inputs: 42 | version: 'v0.x' 43 | 44 | - task: PackageAzureDevOpsExtension@5 45 | inputs: 46 | rootFolder: '$(Build.SourcesDirectory)' 47 | outputPath: '$(vsix)' 48 | extensionVersion: '$(Build.BuildNumber)' 49 | updateTasksVersion: false 50 | 51 | - task: PublishAzureDevOpsExtension@5 52 | inputs: 53 | connectTo: 'AzureRM' 54 | connectedServiceNameAzureRM: azure-devops-marketplace 55 | fileType: 'vsix' 56 | vsixFile: '$(vsix)' 57 | updateTasksVersion: false 58 | noWaitValidation: true 59 | 60 | - task: PublishPipelineArtifact@1 61 | inputs: 62 | targetPath: '$(Build.ArtifactStagingDirectory)' 63 | artifact: 'Output' 64 | 65 | - task: GitHubRelease@1 66 | inputs: 67 | gitHubConnection: 'GitHub - jessehouwing' 68 | repositoryName: '$(Build.Repository.Name)' 69 | action: 'create' 70 | target: '$(Build.SourceVersion)' 71 | tagSource: 'userSpecifiedTag' 72 | tag: '$(Build.BuildNumber)' 73 | title: '$(Build.BuildNumber)' 74 | releaseNotesSource: 'inline' 75 | changeLogCompareToRelease: 'lastFullRelease' 76 | changeLogType: 'commitBased' 77 | -------------------------------------------------------------------------------- /extension-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-gate-condition-editor/9c8f0a8b5176deea676f0d9f4e2639fdd74fb2ff/extension-icon.png -------------------------------------------------------------------------------- /extension-icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-gate-condition-editor/9c8f0a8b5176deea676f0d9f4e2639fdd74fb2ff/extension-icon.psd -------------------------------------------------------------------------------- /overview.md: -------------------------------------------------------------------------------- 1 | Ever wondered what to put in the Success Criteria field? 2 | 3 | ![What to put here?](what-here.png) 4 | 5 | Or queued 182 releases to debug the Success Criteria Condition? This little tool will help you locally evaluate your condition syntax in seconds instead of minutes or hours. 6 | 7 | ![Screenshot](screenshot.png) 8 | 9 | # Learn more 10 | 11 | [I wrote this little utility while debugging a condition for one of my own extensions and blogged about the experience](https://jessehouwing.net/vsts-release-create-complex-release-gate/). 12 | 13 | # Prerequisites 14 | 15 | To run you must have following prerequisites installed: 16 | 17 | * [Azure DevOps Server 2019 or later](https://visualstudio.microsoft.com/downloads/) 18 | 19 | It suffices to simpy install Azure DevOps Server on your workstation (Windows 10 x64). You do not need to configue Azure DevOps Server after installation. The installation is used to load the parser and functions. Since the assemblies required cannot be redistributed, this is the easiest way to ensure the utility can load the required dependencies. -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-gate-condition-editor/9c8f0a8b5176deea676f0d9f4e2639fdd74fb2ff/screenshot.png -------------------------------------------------------------------------------- /vss-extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifestVersion": 1, 3 | "id": "gate-condition-editor", 4 | "name": "Gate Condition Editor for Azure Pipelines", 5 | "description": "Tool to quickly write and test Success Criteria conditions for Release Gates.", 6 | "version": "0.0.5", 7 | "public": true, 8 | "publisher": "jessehouwing", 9 | "galleryFlags": [ 10 | "preview" 11 | ], 12 | "targets": [ 13 | { 14 | "id": "Microsoft.VisualStudio.Services.Integration", 15 | "version": "[17.0,)" 16 | } 17 | ], 18 | "categories": [ 19 | "Azure Pipelines" 20 | ], 21 | "tags": [ 22 | "Gate", 23 | "Release Gate", 24 | "Utility", 25 | "xebia" 26 | ], 27 | "links": { 28 | "home": { 29 | "uri": "https://github.com/jessehouwing/azure-pipelines-gate-condition-editor" 30 | }, 31 | "getstarted": { 32 | "uri": "https://github.com/jessehouwing/azure-pipelines-gate-condition-editor/releases/latest" 33 | }, 34 | "learn": { 35 | "uri": "https://github.com/jessehouwing/azure-pipelines-gate-condition-editor/README.md" 36 | }, 37 | "support": { 38 | "uri": "https://github.com/jessehouwing/azure-pipelines-gate-condition-editor/issues" 39 | }, 40 | "repository": { 41 | "uri": "https://github.com/jessehouwing/azure-pipelines-gate-condition-editor" 42 | }, 43 | "issues": { 44 | "uri": "https://github.com/jessehouwing/azure-pipelines-gate-condition-editor/issues" 45 | }, 46 | "license": { 47 | "uri": "https://github.com/jessehouwing/azure-pipelines-gate-condition-editor/LICENSE.md" 48 | }, 49 | "privacypolicy": { 50 | "uri": "https://github.com/jessehouwing/azure-pipelines-gate-condition-editor/blob/master/PRIVACY.md" 51 | } 52 | }, 53 | "CustomerQnASupport": { 54 | "enableqna": "true", 55 | "url": "https://github.com/jessehouwing/azure-pipelines-gate-condition-editor/issues" 56 | }, 57 | "badges": [ 58 | { 59 | "href": "https://github.com/sponsors/jessehouwing", 60 | "uri": "https://img.shields.io/github/sponsors/jessehouwing", 61 | "description": "GitHub Sponsors" 62 | } 63 | ], 64 | "repository": { 65 | "type": "git", 66 | "uri": "https://github.com/jessehouwing/azure-pipelines-gate-condition-editor.git" 67 | }, 68 | "icons": { 69 | "default": "extension-icon.png" 70 | }, 71 | "branding": { 72 | "color": "#1E1E1E", 73 | "theme": "dark" 74 | }, 75 | "content": { 76 | "details": { 77 | "path": "overview.md" 78 | }, 79 | "license": { 80 | "path": "LICENSE.md" 81 | }, 82 | "privacy": { 83 | "path": "PRIVACY.md" 84 | } 85 | }, 86 | "files": [ 87 | { 88 | "path": "screenshot.png", 89 | "addressable": true 90 | }, 91 | { 92 | "path": "what-here.png", 93 | "addressable": true 94 | } 95 | ] 96 | } 97 | -------------------------------------------------------------------------------- /what-here.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-gate-condition-editor/9c8f0a8b5176deea676f0d9f4e2639fdd74fb2ff/what-here.png --------------------------------------------------------------------------------