Skip to content

Commit d8516b5

Browse files
azp: improve VSCode extension (#604)
- ask for all parameters - dismissing the input of a value with default / selecting the default will follow changes to the default value of the pipeline - the task stops now - if you change the type of a parameter while the task is in watch mode and your own input is not applicable - if you add a required parameter - allow to copy the default validate and expand task as Cli Command
1 parent f7e0cac commit d8516b5

9 files changed

Lines changed: 147 additions & 92 deletions

File tree

src/Sdk/AzurePipelines/AzureDevops.cs

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,33 +1328,34 @@ public static async Task<MappingToken> ReadTemplate(Runner.Server.Azure.Devops.C
13281328
var defCtxData = def == null ? null : await ConvertValue(context, def, type, values);
13291329
if (cparameters?.TryGetValue(name, out var value) == true)
13301330
{
1331-
parametersData[name] = await ConvertValue(context, value, type, values);
1331+
try {
1332+
parametersData[name] = await ConvertValue(context, value, type, values);
1333+
continue;
1334+
} catch (Exception ex) when(context.ParametersProvider != null) {
1335+
var stype = type.AssertLiteralString("parameters.*.type");
1336+
await context.ParametersProvider.ReportInvalidParameterValue(name, stype, $"Cannot convert provided value for for the '{name}' parameter: {ex.Message}");
1337+
}
13321338
}
1333-
else
1334-
{
1335-
if (def == null) // handle missing required parameter
1336-
{
1337-
value = null;
1338-
if(context.RequiredParametersProvider != null) {
1339-
var stype = type.AssertLiteralString("parameters.*.type");
1340-
while(true) {
1341-
try {
1342-
value = await context.RequiredParametersProvider.GetRequiredParameter(name, stype, values == null || values is NullToken ? null : values.AssertSequence("parameters.*.values").Select(s => s.AssertLiteralString("parameters.*.values.*")));
1343-
if(value != null) {
1344-
parametersData[name] = await ConvertValue(context, value, type, values);
1345-
}
1346-
break;
1347-
} catch(Exception ex) {
1348-
await context.RequiredParametersProvider.ReportInvalidParameterValue(name, stype, $"Cannot convert provided value for for the '{name}' parameter: {ex.Message}");
1349-
}
1350-
};
1351-
}
1352-
if(!parametersData.ContainsKey(name)) {
1353-
templateContext.Error(mparam, $"A value for the '{name}' parameter must be provided.");
1339+
value = null;
1340+
if(context.ParametersProvider != null) {
1341+
var stype = type.AssertLiteralString("parameters.*.type");
1342+
while(true) {
1343+
try {
1344+
value = await context.ParametersProvider.GetParameter(name, stype, values == null || values is NullToken ? null : values.AssertSequence("parameters.*.values").Select(s => s.AssertLiteralString("parameters.*.values.*")), def);
1345+
if(value != null) {
1346+
parametersData[name] = await ConvertValue(context, value, type, values);
1347+
}
1348+
break;
1349+
} catch(Exception ex) {
1350+
await context.ParametersProvider.ReportInvalidParameterValue(name, stype, $"Cannot convert provided value for for the '{name}' parameter: {ex.Message}");
13541351
}
1355-
} else {
1356-
parametersData[name] = defCtxData;
1357-
}
1352+
};
1353+
}
1354+
if(value == null && def != null) {
1355+
parametersData[name] = defCtxData;
1356+
}
1357+
if(!parametersData.ContainsKey(name)) {
1358+
templateContext.Error(mparam, $"A value for the '{name}' parameter must be provided.");
13581359
}
13591360
}
13601361

@@ -1363,6 +1364,10 @@ public static async Task<MappingToken> ReadTemplate(Runner.Server.Azure.Devops.C
13631364
foreach (var unexpectedParameter in cparameters.Where(kv => !parametersData.ContainsKey(kv.Key)))
13641365
{
13651366
templateContext.Error(unexpectedParameter.Value ?? parameters, $"Unexpected parameter '{unexpectedParameter.Key}'");
1367+
if(context.ParametersProvider != null)
1368+
{
1369+
await context.ParametersProvider.ReportInvalidParameterValue(unexpectedParameter.Key, null, $"Unexpected parameter '{unexpectedParameter.Key}'");
1370+
}
13661371
}
13671372
}
13681373
}

src/Sdk/AzurePipelines/Context.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class Context {
1616
public Dictionary<string, string> Repositories { get; set; }
1717

1818
public ITaskByNameAndVersionProvider TaskByNameAndVersion { get; set; }
19-
public IRequiredParametersProvider RequiredParametersProvider { get; set; }
19+
public IParametersProvider ParametersProvider { get; set; }
2020
public List<string> FileTable { get; set; } = new List<string>();
2121
public int Column { get; internal set; }
2222
public int Row { get; internal set; }
@@ -34,7 +34,7 @@ public Context ChildContext(MappingToken template, string path = null) {
3434
Repositories = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
3535
}
3636
var childContext = Clone();
37-
childContext.RequiredParametersProvider = null;
37+
childContext.ParametersProvider = null;
3838
childContext.AutoCompleteMatches = null;
3939
childContext.Column = 0;
4040
childContext.Row = 0;

src/Sdk/AzurePipelines/IRequiredParametersProvider.cs renamed to src/Sdk/AzurePipelines/IParametersProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
using GitHub.DistributedTask.ObjectTemplating.Tokens;
33

44
namespace Runner.Server.Azure.Devops {
5-
public interface IRequiredParametersProvider {
6-
Task<TemplateToken> GetRequiredParameter(string name, string type, System.Collections.Generic.IEnumerable<string> enumerable);
5+
public interface IParametersProvider {
6+
Task<TemplateToken> GetParameter(string name, string type, System.Collections.Generic.IEnumerable<string> enumerable, TemplateToken defaultValue);
77
Task ReportInvalidParameterValue(string name, string type, string message);
88
}
99
}

src/azure-pipelines-vscode-ext/CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1-
### v0.3.1
1+
### v0.3.2 (Preview)
22
- remove additional space caused by export task as Cli Command
33
- fix saving a single untitled pipeline file from browser broke syntax checker due to invalid uri joining
4+
- ask for all parameters
5+
- dismissing the input of a value with default / selecting the default will follow changes to the default value of the pipeline
6+
- the task stops now
7+
- if you change the type of a parameter while the task is in watch mode and your own input is not applicable
8+
- if you add a required parameter
9+
- allow to copy the default validate and expand task as Cli Command
410

511
### v0.3.1 (Preview)
612
- fix -q flag had no space between cli command and hypen

src/azure-pipelines-vscode-ext/ext-core/Interop.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ public static partial class Interop {
99
internal static partial Task Sleep(int time);
1010
[JSImport("log", "extension.js")]
1111
internal static partial void Log(JSObject handle, int type, string message);
12-
[JSImport("requestRequiredParameter", "extension.js")]
13-
internal static partial Task<string> RequestRequiredParameter(JSObject handle, string name, string type, string[] values);
12+
[JSImport("requestParameter", "extension.js")]
13+
internal static partial Task<string> RequestParameter(JSObject handle, string name, string type, string[] values, string defaultValue);
1414
[JSImport("error", "extension.js")]
1515
internal static partial Task Error(JSObject handle, string message);
1616
[JSImport("autocompletelist", "extension.js")]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using GitHub.DistributedTask.ObjectTemplating.Tokens;
2+
using Runner.Server.Azure.Devops;
3+
using System.Collections.Generic;
4+
using System.Runtime.InteropServices.JavaScript;
5+
using System.Threading.Tasks;
6+
using System.Linq;
7+
using GitHub.DistributedTask.Pipelines.ContextData;
8+
using Newtonsoft.Json;
9+
10+
public class ParametersProvider : IParametersProvider {
11+
JSObject handle;
12+
13+
public ParametersProvider(JSObject handle) {
14+
this.handle = handle;
15+
}
16+
17+
public async Task<TemplateToken> GetParameter(string name, string type, IEnumerable<string> enumerable, TemplateToken defaultValue) {
18+
var result = await Interop.RequestParameter(handle, name, type, enumerable?.ToArray(), JsonConvert.SerializeObject(defaultValue?.ToContextData()?.ToJToken()));
19+
return AzurePipelinesUtils.ConvertStringToTemplateToken(result);
20+
}
21+
22+
public Task ReportInvalidParameterValue(string name, string type, string message)
23+
{
24+
if(type == null) {
25+
// implementation detail to stop the task for removed parameters
26+
return Interop.RequestParameter(handle, name, type, null, null).ContinueWith(t => {});
27+
}
28+
return Interop.Message(handle, 2, $"Provided value of {name} is not a valid {type}: {message}");
29+
}
30+
}

src/azure-pipelines-vscode-ext/ext-core/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public static async Task<string> ExpandCurrentPipeline(JSObject handle, string c
8989
FileProvider = new MyFileProvider(handle),
9090
TraceWriter = new TraceWriter(handle),
9191
Flags = GitHub.DistributedTask.Expressions2.ExpressionFlags.DTExpressionsV1 | GitHub.DistributedTask.Expressions2.ExpressionFlags.ExtendedDirectives,
92-
RequiredParametersProvider = new RequiredParametersProvider(handle),
92+
ParametersProvider = new ParametersProvider(handle),
9393
VariablesProvider = new VariablesProvider { Variables = JsonConvert.DeserializeObject<Dictionary<string, string>>(variables) }
9494
};
9595
string yaml = null;
@@ -136,7 +136,7 @@ public static async Task ParseCurrentPipeline(JSObject handle, string currentFil
136136
FileProvider = new MyFileProvider(handle),
137137
TraceWriter = new TraceWriter(handle),
138138
Flags = GitHub.DistributedTask.Expressions2.ExpressionFlags.DTExpressionsV1 | GitHub.DistributedTask.Expressions2.ExpressionFlags.ExtendedDirectives,
139-
RequiredParametersProvider = new RequiredParametersProvider(handle),
139+
ParametersProvider = new ParametersProvider(handle),
140140
VariablesProvider = new VariablesProvider { Variables = new Dictionary<string, string>() },
141141
Column = column,
142142
Row = row,

src/azure-pipelines-vscode-ext/ext-core/RequiredParametersProvider.cs

Lines changed: 0 additions & 24 deletions
This file was deleted.

src/azure-pipelines-vscode-ext/index.js

Lines changed: 72 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -213,23 +213,40 @@ function activate(context) {
213213
break;
214214
}
215215
},
216-
requestRequiredParameter: async (handle, name, type, values) => {
216+
requestParameter: async (handle, name, type, values, def) => {
217+
if(type === null) {
218+
// implementation detail to stop the task for removed parameters
219+
handle.stopTask = true;
220+
return;
221+
}
217222
if(!handle.askForInput) {
223+
if(!def || name in handle.parameters) {
224+
handle.stopTask = true;
225+
}
218226
return;
219227
}
220228
if(type === "boolean") {
221229
values ??= ["true", "false"];
222230
}
223231
var value = undefined;
224232
var acceptYAML = false;
233+
var prefix = def ? "" : "required ";
225234
if(values && values.length) {
235+
var pdef = def ? JSON.parse(def) : undefined;
236+
if(pdef != undefined) {
237+
values = values.filter(v => v !== pdef.toString());
238+
values.unshift(pdef.toString());
239+
}
226240
value = await vscode.window.showQuickPick(values, {
227241
canPickMany: type.endsWith("List"),
228-
placeHolder: "value",
242+
placeHolder: def || "value",
229243
prompt: name,
230244
ignoreFocusOut: true,
231-
title: "Select the required Parameter " + name + " of Type " + type
245+
title: "Select the " + prefix + "Parameter " + name + " of Type " + type
232246
});
247+
if(def && value === def) {
248+
value = undefined;
249+
}
233250
} else {
234251
switch(type) {
235252
case "stringList":
@@ -248,16 +265,23 @@ function activate(context) {
248265
acceptYAML = true;
249266
break;
250267
}
268+
let rawValue = def ? acceptYAML ? def : JSON.parse(def).toString() : undefined;
251269
value = await vscode.window.showInputBox({
252270
ignoreFocusOut: true,
253-
placeHolder: "value",
271+
placeHolder: rawValue || "value",
254272
prompt: name,
255-
title: "Provide required Parameter " + name + " of Type " + type + (acceptYAML ? " in YAML notation" : "as free text"),
273+
value: rawValue,
274+
title: "Provide " + prefix + "Parameter " + name + " of Type " + type + (acceptYAML ? " in YAML notation" : "as free text"),
256275
});
276+
if(def && value === rawValue) {
277+
value = undefined;
278+
}
257279
}
258280
if(value === undefined) {
259281
value = null;
260-
handle.stopTask = true;
282+
if(!def || name in handle.parameters) {
283+
handle.stopTask = true;
284+
}
261285
return null;
262286
}
263287
if(!acceptYAML) {
@@ -521,6 +545,9 @@ function activate(context) {
521545
}
522546
state.parameters = rawparams;
523547
}
548+
if(handle.stopTask) {
549+
state.stopTask = true;
550+
}
524551
}
525552

526553
if(result || syntaxOnly) {
@@ -544,37 +571,43 @@ function activate(context) {
544571
}
545572
};
546573

574+
let createExpandTask = () => new vscode.Task(
575+
{
576+
type: "azure-pipelines-vscode-ext",
577+
program: vscode.window.activeTextEditor?.document?.uri?.toString(),
578+
watch: true,
579+
preview: true,
580+
autoClosePreview: true
581+
},
582+
vscode.TaskScope.Workspace,
583+
"Azure Pipeline Preview (watch)",
584+
"azure-pipelines",
585+
executor,
586+
null
587+
);
588+
547589
var expandAzurePipelineCommand = () => {
548590
return vscode.tasks.executeTask(
549-
new vscode.Task({
550-
type: "azure-pipelines-vscode-ext",
551-
program: vscode.window.activeTextEditor?.document?.uri?.toString(),
552-
watch: true,
553-
preview: true,
554-
autoClosePreview: true
555-
},
556-
vscode.TaskScope.Workspace,
557-
"Azure Pipeline Preview (watch)",
558-
"azure-pipelines",
559-
executor,
560-
null
561-
)
591+
createExpandTask()
562592
);
563593
}
564594

595+
let createValidateTask = () => new vscode.Task(
596+
{
597+
type: "azure-pipelines-vscode-ext",
598+
program: vscode.window.activeTextEditor?.document?.uri?.toString(),
599+
watch: true,
600+
},
601+
vscode.TaskScope.Workspace,
602+
"Azure Pipeline Validate (watch)",
603+
"azure-pipelines",
604+
executor,
605+
null
606+
);
607+
565608
var validateAzurePipelineCommand = () => {
566609
return vscode.tasks.executeTask(
567-
new vscode.Task({
568-
type: "azure-pipelines-vscode-ext",
569-
program: vscode.window.activeTextEditor?.document?.uri?.toString(),
570-
watch: true,
571-
},
572-
vscode.TaskScope.Workspace,
573-
"Azure Pipeline Validate (watch)",
574-
"azure-pipelines",
575-
executor,
576-
null
577-
)
610+
createValidateTask()
578611
);
579612
}
580613

@@ -605,8 +638,9 @@ function activate(context) {
605638
context.subscriptions.push(vscode.commands.registerCommand('extension.validateAzurePipeline', () => validateAzurePipelineCommand()));
606639

607640
context.subscriptions.push(vscode.commands.registerCommand('azure-pipelines-vscode-ext.copyTaskAsCommand', () => {
608-
vscode.tasks.fetchTasks({ type: "azure-pipelines-vscode-ext" }).then(tasks =>
609-
vscode.window.showQuickPick(tasks.filter(t => t.definition.program).map(t => t.name), { placeHolder: "Select Task to copy as Runner.Client Command" }).then(async name => {
641+
vscode.tasks.fetchTasks({ type: "azure-pipelines-vscode-ext" }).then(tasks => {
642+
tasks.unshift(createValidateTask(), createExpandTask())
643+
return vscode.window.showQuickPick(tasks.filter(t => t.definition.program).map(t => t.name), { placeHolder: "Select Task to copy as Runner.Client Command" }).then(async name => {
610644
if(name) {
611645
var task = tasks.find(t => t.name === name);
612646
if(task) {
@@ -627,7 +661,8 @@ function activate(context) {
627661
}
628662
}
629663
await vscode.window.showErrorMessage("No Task selected");
630-
}).catch(err => vscode.window.showErrorMessage(err.toString())));
664+
}).catch(err => vscode.window.showErrorMessage(err.toString()));
665+
});
631666
}));
632667

633668
var statusbar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right);
@@ -1091,7 +1126,10 @@ function activate(context) {
10911126
setTimeout(resolve, 1);
10921127
});
10931128
inProgress = false;
1094-
if(!args.watch) {
1129+
if(self?.stopTask) {
1130+
task.info("Parameters changed, please re-run the task when done");
1131+
}
1132+
if(!args.watch || self?.stopTask) {
10951133
close();
10961134
}
10971135
if(waiting) {

0 commit comments

Comments
 (0)