Skip to content

Commit

Permalink
supporting scaffolders having multiple categories (#3035)
Browse files Browse the repository at this point in the history
* supporting scaffolders having multiple categories

* updated IScaffolder interface to use IEnumerable

* small fix

* hashing the category related telemetry values.
  • Loading branch information
deepchoudhery authored Oct 24, 2024
1 parent 31947f6 commit 424243b
Show file tree
Hide file tree
Showing 13 changed files with 71 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ namespace Microsoft.DotNet.Scaffolding.Core.Builder;

internal class ScaffoldBuilder(string name) : IScaffoldBuilder
{
private const string DEFAULT_CATEGORY = "General";
private const string DEFAULT_CATEGORY = "All";

private readonly List<ScaffolderOption> _options = [];
private readonly List<ScaffoldStepPreparer> _stepPreparers = [];
private readonly string _name = FixName(name);
private string? _displayName;
private string? _category;
private HashSet<string> _categories = [DEFAULT_CATEGORY];
private string? _description;

internal string Name => _name;
internal string DisplayName => _displayName ?? System.Globalization.CultureInfo.CurrentUICulture.TextInfo.ToTitleCase(name);
internal string Category => _category ?? DEFAULT_CATEGORY;
internal HashSet<string> Categories => _categories;
internal string? Description => _description;
internal IEnumerable<ScaffolderOption> Options => _options;
internal IEnumerable<ScaffoldStepPreparer> StepPreparers => _stepPreparers;
Expand All @@ -32,7 +32,7 @@ public IScaffoldBuilder WithDisplayName(string displayName)

public IScaffoldBuilder WithCategory(string category)
{
_category = category;
_categories.Add(category);
return this;
}

Expand Down Expand Up @@ -78,7 +78,7 @@ public IScaffolder Build(IServiceProvider serviceProvider)
steps.Add((ScaffoldStep)stepInstance);
}

return new Scaffolder(Name, DisplayName, Category, Description, _options, steps, _stepPreparers);
return new Scaffolder(Name, DisplayName, Categories.ToList(), Description, _options, steps, _stepPreparers);
}

private static string FixName(string name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ private static CommandInfo ToCommandInfo(this IScaffolder scaffolder)
{
Name = scaffolder.Name,
DisplayName = scaffolder.DisplayName,
DisplayCategory = scaffolder.Category,
DisplayCategories = scaffolder.Categories.ToList(),
Description = scaffolder.Description,
Parameters = scaffolder.Options.Select(o => o.ToParameter()).ToArray()
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ internal class CommandInfo
{
public required string Name { get; init; }
public required string DisplayName { get; init; }
public required string DisplayCategory { get; init; }
public required List<string> DisplayCategories { get; init; }
public string? Description { get; set; }
public required Parameter[] Parameters { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ public interface IScaffolder
string DisplayName { get; }

/// <summary>
/// The category in which the scaffolder will be organized in the dotnet-scaffold interactive UI
/// The categories in which the scaffolder will be organized in the dotnet-scaffold interactive UI (atleast one default 'All')
/// </summary>
string Category { get; }
IEnumerable<string> Categories { get; }

/// <summary>
/// The description of the scaffolder as it will be displayed in the dotnet-scaffold interactive UI
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,22 @@ public class Scaffolder : IScaffolder
{
private readonly string _name;
private readonly string _displayName;
private readonly string _category;
private readonly List<string> _categories;
private readonly string? _description;
private readonly List<ScaffolderOption> _options;
private readonly List<ScaffoldStep> _steps;
private readonly List<ScaffoldStepPreparer> _preparers;
public string Name => _name;
public string DisplayName => _displayName;
public string Category => _category;
public IEnumerable<string> Categories => _categories;
public string? Description => _description;
public IEnumerable<ScaffolderOption> Options => _options;

internal Scaffolder(string name, string displayName, string category, string? description, List<ScaffolderOption> options, List<ScaffoldStep> steps, List<ScaffoldStepPreparer> preparers)
internal Scaffolder(string name, string displayName, List<string> categories, string? description, List<ScaffolderOption> options, List<ScaffoldStep> steps, List<ScaffoldStepPreparer> preparers)
{
_name = name;
_displayName = displayName;
_category = category;
_categories = categories;
_description = description;
_options = options;
_steps = steps;
Expand Down
1 change: 1 addition & 0 deletions src/dotnet-scaffolding/dotnet-scaffold-aspnet/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ public static void Main(string[] args)
builder.AddScaffolder("blazor-identity")
.WithDisplayName("Blazor Identity")
.WithCategory("Blazor")
.WithCategory("Identity")
.WithDescription("Add blazor identity to a project.")
.WithOptions([projectOption, dataContextClassRequiredOption, identityDbProviderRequiredOption, overwriteOption, prereleaseOption])
.WithStep<ValidateIdentityStep>(config =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ internal static class FlowContextProperties
public const string TargetFrameworkName = nameof(TargetFrameworkName);
public const string RemainingArgs = nameof(RemainingArgs);
public const string DotnetToolComponents = nameof(DotnetToolComponents);
public const string ScaffoldingCategory = nameof(ScaffoldingCategory);
public const string ScaffoldingCategories = nameof(ScaffoldingCategories);
public const string ChosenCategory = nameof(ChosenCategory);
public const string TelemetryEnvironmentVariables = nameof(TelemetryEnvironmentVariables);
}
9 changes: 7 additions & 2 deletions src/dotnet-scaffolding/dotnet-scaffold/Flow/FlowExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,14 @@ internal static class FlowContextExtensions
return context.GetValueOrThrow<IList<KeyValuePair<string, CommandInfo>>>(FlowContextProperties.CommandInfos, throwIfEmpty);
}

public static string? GetScaffoldingCategory(this IFlowContext context, bool throwIfEmpty = false)
public static List<string>? GetScaffoldingCategories(this IFlowContext context, bool throwIfEmpty = false)
{
return context.GetValueOrThrow<string>(FlowContextProperties.ScaffoldingCategory, throwIfEmpty);
return context.GetValueOrThrow<List<string>?>(FlowContextProperties.ScaffoldingCategories, throwIfEmpty);
}

public static string? GetChosenCategory(this IFlowContext context, bool throwIfEmpty = false)
{
return context.GetValueOrThrow<string>(FlowContextProperties.ChosenCategory, throwIfEmpty);
}

public static string? GetSourceProjectPath(this IFlowContext context, bool throwIfEmpty = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public CategoryDiscovery(IDotNetToolService dotnetToolService, DotNetToolInfo? c
var displayCategories = new List<string>();
//only get categories from the picked DotNetToolInfo
displayCategories = (_componentPicked != null ? allCommands?.Where(x => x.Key.Equals(_componentPicked.Command)) : allCommands)
?.Select(y => y.Value.DisplayCategory)
?.SelectMany(y => y.Value.DisplayCategories)
?.Distinct()
?.Order()
?.ToList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public CategoryPickerFlowStep(ILogger logger, IDotNetToolService dotnetToolServi

public ValueTask ResetAsync(IFlowContext context, CancellationToken cancellationToken)
{
context.Unset(FlowContextProperties.ScaffoldingCategory);
context.Unset(FlowContextProperties.ScaffoldingCategories);
context.Unset(FlowContextProperties.ChosenCategory);
context.Unset(FlowContextProperties.ComponentName);
context.Unset(FlowContextProperties.ComponentObj);
context.Unset(FlowContextProperties.CommandName);
Expand Down Expand Up @@ -57,7 +58,7 @@ public ValueTask<FlowStepResult> RunAsync(IFlowContext context, CancellationToke
}
else
{
SelectCategory(context, displayCategory);
SelectChosenCategory(context, displayCategory);
}

return new ValueTask<FlowStepResult>(FlowStepResult.Success);
Expand Down Expand Up @@ -92,15 +93,24 @@ public ValueTask<FlowStepResult> ValidateUserInputAsync(IFlowContext context, Ca

SelectComponent(context, dotnetToolComponent);
SelectCommand(context, commandInfo);
SelectCategory(context, commandInfo.DisplayCategory);
SelectCategories(context, commandInfo.DisplayCategories);
return new ValueTask<FlowStepResult>(FlowStepResult.Success);
}

private void SelectCategory(IFlowContext context, string categoryName)
private void SelectCategories(IFlowContext context, List<string> categories)
{
context.Set(new FlowProperty(
FlowContextProperties.ScaffoldingCategory,
categoryName,
FlowContextProperties.ScaffoldingCategories,
categories,
"Scaffolding Categories",
isVisible: false));
}

private void SelectChosenCategory(IFlowContext context, string category)
{
context.Set(new FlowProperty(
FlowContextProperties.ChosenCategory,
category,
"Scaffolding Category",
isVisible: true));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,28 @@ public CommandDiscovery(IDotNetToolService dotnetToolService, DotNetToolInfo? co
allCommands = allCommands?.Where(x => x.Key.Equals(_componentPicked.Command)).ToList();
}

var scaffoldingCategory = context.GetScaffoldingCategory();
List<string> scaffoldingCategories = [];
var scaffoldingCategory = context.GetChosenCategory();
if (string.IsNullOrEmpty(scaffoldingCategory))
{
//get all categories for non-interactive scenario (since no chosen one was found)
var possibleScaffoldingCategories = context.GetScaffoldingCategories();
if (possibleScaffoldingCategories is null || possibleScaffoldingCategories.Count == 0)
{
return null;
}
else
{
scaffoldingCategories.AddRange(possibleScaffoldingCategories);
}
}
else
{
scaffoldingCategories.Add(scaffoldingCategory);
}

var allCommandsByCategory = allCommands?
.Where(x => x.Value.DisplayCategory.Equals(scaffoldingCategory))
.Where(x => x.Value.DisplayCategories.Intersect(scaffoldingCategories).Any())
.OrderBy(kvp => kvp.Value.DisplayName)
.ToList();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public ValueTask<FlowStepResult> ValidateUserInputAsync(IFlowContext context, Ca

var parameterValues = GetAllParameterValues(context, commandObj);
var envVars = context.GetTelemetryEnvironmentVariables();
var chosenCategory = context.GetChosenCategory();
if (!string.IsNullOrEmpty(dotnetToolInfo.Command) && parameterValues.Count != 0 && !string.IsNullOrEmpty(commandObj.Name))
{
var componentExecutionString = $"{dotnetToolInfo.Command} {string.Join(" ", parameterValues)}";
Expand All @@ -63,7 +64,7 @@ public ValueTask<FlowStepResult> ValidateUserInputAsync(IFlowContext context, Ca
(s) => AnsiConsole.Console.MarkupLineInterpolated($"[red]{s}[/]"));
});

_telemetryService.TrackEvent(new CommandExecuteTelemetryEvent(dotnetToolInfo, commandObj, exitCode));
_telemetryService.TrackEvent(new CommandExecuteTelemetryEvent(dotnetToolInfo, commandObj, exitCode, chosenCategory));
if (exitCode != null)
{
if (exitCode != 0)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.DotNet.Scaffolding.Core.ComponentModel;
using Microsoft.DotNet.Scaffolding.Internal.Extensions;
using Microsoft.DotNet.Scaffolding.Internal.Telemetry;

namespace Microsoft.DotNet.Tools.Scaffold.Telemetry;

internal class CommandExecuteTelemetryEvent : TelemetryEventBase
{
private const string TelemetryEventName = "CommandExecute";
public CommandExecuteTelemetryEvent(DotNetToolInfo dotnetToolInfo, CommandInfo commandInfo, int? exitCode) : base(TelemetryEventName)
public CommandExecuteTelemetryEvent(DotNetToolInfo dotnetToolInfo, CommandInfo commandInfo, int? exitCode, string? chosenCategory = null) : base(TelemetryEventName)
{
SetProperty("PackageName", dotnetToolInfo.PackageName);
SetProperty("Version", dotnetToolInfo.Version);
SetProperty("ToolName", dotnetToolInfo.Command);
SetProperty("ToolLevel", dotnetToolInfo.IsGlobalTool ? TelemetryConstants.GlobalTool : TelemetryConstants.LocalTool);
SetProperty("CommandName", commandInfo.Name);
SetProperty("CommandCategory", commandInfo.DisplayCategory);
SetProperty("AllCommandCategories", string.Join(",", commandInfo.DisplayCategories).Hash());
if (!string.IsNullOrEmpty(chosenCategory))
{
SetProperty("ChosenCategory", chosenCategory.Hash());
}

SetProperty("Result", exitCode is null ? TelemetryConstants.Unknown : exitCode == 0 ? TelemetryConstants.Success : TelemetryConstants.Failure);
}
}

0 comments on commit 424243b

Please sign in to comment.