Skip to content

Commit

Permalink
Show recipe error instead if throwing an exeption
Browse files Browse the repository at this point in the history
Fix #16056

Related Work Items: #1605
  • Loading branch information
MikeAlhayek committed May 23, 2024
1 parent 77c69c6 commit 814f4f6
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 53 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
using OrchardCore.ContentManagement.Metadata;
using OrchardCore.ContentManagement.Metadata.Models;
using OrchardCore.ContentManagement.Metadata.Records;
Expand All @@ -16,9 +17,14 @@ public class ContentDefinitionStep : IRecipeStepHandler
{
private readonly IContentDefinitionManager _contentDefinitionManager;

public ContentDefinitionStep(IContentDefinitionManager contentDefinitionManager)
protected readonly IStringLocalizer S;

public ContentDefinitionStep(
IContentDefinitionManager contentDefinitionManager,
IStringLocalizer<ContentDefinitionStep> stringLocalizer)
{
_contentDefinitionManager = contentDefinitionManager;
S = stringLocalizer;
}

public async Task ExecuteAsync(RecipeExecutionContext context)
Expand All @@ -35,20 +41,21 @@ public async Task ExecuteAsync(RecipeExecutionContext context)
var newType = await _contentDefinitionManager.LoadTypeDefinitionAsync(contentType.Name)
?? new ContentTypeDefinition(contentType.Name, contentType.DisplayName);

await UpdateContentTypeAsync(newType, contentType);
await UpdateContentTypeAsync(newType, contentType, context);
}

foreach (var contentPart in step.ContentParts)
{
var newPart = await _contentDefinitionManager.LoadPartDefinitionAsync(contentPart.Name)
?? new ContentPartDefinition(contentPart.Name);

await UpdateContentPartAsync(newPart, contentPart);
await UpdateContentPartAsync(newPart, contentPart, context);
}
}

private Task UpdateContentTypeAsync(ContentTypeDefinition type, ContentTypeDefinitionRecord record)
=> _contentDefinitionManager.AlterTypeDefinitionAsync(type.Name, builder =>
private Task UpdateContentTypeAsync(ContentTypeDefinition type, ContentTypeDefinitionRecord record, RecipeExecutionContext context)
{
return _contentDefinitionManager.AlterTypeDefinitionAsync(type.Name, builder =>
{
if (!string.IsNullOrEmpty(record.DisplayName))
{
Expand All @@ -60,31 +67,44 @@ private Task UpdateContentTypeAsync(ContentTypeDefinition type, ContentTypeDefin
{
if (string.IsNullOrEmpty(part.PartName))
{
throw new InvalidOperationException($"Unable to add content-part to the '{type.Name}' content-type. The part name cannot be null or empty.");
context.Errors.Add(S["Unable to add content-part to the '{0}' content-type. The part name cannot be null or empty.", type.Name]);
continue;
}
builder.WithPart(part.Name, part.PartName, partBuilder => partBuilder.MergeSettings(part.Settings));
}
});
}

private Task UpdateContentPartAsync(ContentPartDefinition part, ContentPartDefinitionRecord record)
=> _contentDefinitionManager.AlterPartDefinitionAsync(part.Name, builder =>
private Task UpdateContentPartAsync(ContentPartDefinition part, ContentPartDefinitionRecord record, RecipeExecutionContext context)
{
return _contentDefinitionManager.AlterPartDefinitionAsync(part.Name, builder =>
{
builder.MergeSettings(record.Settings);
foreach (var field in record.ContentPartFieldDefinitionRecords)
{
if (string.IsNullOrEmpty(field.Name))
{
context.Errors.Add(S["Unable to add content-field to the '{0}' content-part. The part name cannot be null or empty.", part.Name]);
continue;
}
builder.WithField(field.Name, fieldBuilder =>
{
fieldBuilder.OfType(field.FieldName);
fieldBuilder.MergeSettings(field.Settings);
});
}
});
}

private sealed class ContentDefinitionStepModel
{
public ContentTypeDefinitionRecord[] ContentTypes { get; set; } = [];

public ContentPartDefinitionRecord[] ContentParts { get; set; } = [];
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Text.Json;
Expand All @@ -8,11 +9,13 @@
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using OrchardCore.Admin;
using OrchardCore.Deployment.Services;
using OrchardCore.Deployment.ViewModels;
using OrchardCore.DisplayManagement.Notify;
using OrchardCore.Mvc.Utilities;
using OrchardCore.Recipes.Models;

namespace OrchardCore.Deployment.Controllers
{
Expand All @@ -22,21 +25,24 @@ public class ImportController : Controller
private readonly IDeploymentManager _deploymentManager;
private readonly IAuthorizationService _authorizationService;
private readonly INotifier _notifier;
private readonly ILogger _logger;

protected readonly IHtmlLocalizer H;
protected readonly IStringLocalizer S;

public ImportController(
IDeploymentManager deploymentManager,
IAuthorizationService authorizationService,
INotifier notifier,
ILogger<ImportController> logger,
IHtmlLocalizer<ImportController> htmlLocalizer,
IStringLocalizer<ImportController> stringLocalizer
)
{
_deploymentManager = deploymentManager;
_authorizationService = authorizationService;
_notifier = notifier;

_logger = logger;
H = htmlLocalizer;
S = stringLocalizer;
}
Expand Down Expand Up @@ -148,6 +154,21 @@ public async Task<IActionResult> Json(ImportJsonViewModel model)

await _notifier.SuccessAsync(H["Recipe imported."]);
}
catch (RecipeExecutionException e)
{
_logger.LogError(e, "Unable to import a recipe from JSON input.");

foreach (var entry in e.StepResult.Errors)
{
ModelState.AddModelError(nameof(model.Json), string.Join(' ', entry.Value));
}
}
catch (Exception e)
{
_logger.LogError(e, "Unable to import a recipe from JSON input.");

ModelState.AddModelError(string.Empty, S["Unexpected error occurred while importing the recipe."]);
}
finally
{
if (Directory.Exists(tempArchiveFolder))
Expand Down
16 changes: 11 additions & 5 deletions src/OrchardCore.Modules/OrchardCore.Layers/Recipes/LayerStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using OrchardCore.Json;
using OrchardCore.Layers.Models;
Expand All @@ -25,16 +26,20 @@ public class LayerStep : IRecipeStepHandler
private readonly IEnumerable<IConditionFactory> _factories;
private readonly JsonSerializerOptions _serializationOptions;

protected readonly IStringLocalizer S;

public LayerStep(
ILayerService layerService,
IConditionIdGenerator conditionIdGenerator,
IEnumerable<IConditionFactory> factories,
IOptions<DocumentJsonSerializerOptions> serializationOptions)
IOptions<DocumentJsonSerializerOptions> serializationOptions,
IStringLocalizer<LayerStep> stringLocalizer)
{
_layerService = layerService;
_conditionIdGenerator = conditionIdGenerator;
_factories = factories;
_serializationOptions = serializationOptions.Value.SerializerOptions;
S = stringLocalizer;
}

public async Task ExecuteAsync(RecipeExecutionContext context)
Expand Down Expand Up @@ -76,7 +81,9 @@ public async Task ExecuteAsync(RecipeExecutionContext context)
}
else
{
throw new InvalidOperationException($"The layer '{nameof(layer.Name)}' is required.");
context.Errors.Add(S["The layer '{nameof(layer.Name)}' is required."]);

continue;
}

if (layerStep.LayerRule != null)
Expand Down Expand Up @@ -112,10 +119,9 @@ public async Task ExecuteAsync(RecipeExecutionContext context)

if (unknownTypes.Count != 0)
{
var prefix = "No changes have been made. The following types of conditions cannot be added:";
var suffix = "Please ensure that the related features are enabled to add these types of conditions.";
context.Errors.Add(S["No changes have been made. The following types of conditions cannot be added: {0}. Please ensure that the related features are enabled to add these types of conditions.", string.Join(", ", unknownTypes)]);

throw new InvalidOperationException($"{prefix} {string.Join(", ", unknownTypes)}. {suffix}");
return;
}

await _layerService.UpdateAsync(allLayers);
Expand Down
3 changes: 2 additions & 1 deletion src/OrchardCore.Modules/OrchardCore.Recipes/AdminMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public Task BuildNavigationAsync(string name, NavigationBuilder builder)
builder
.Add(S["Configuration"], configuration => configuration
.Add(S["Recipes"], S["Recipes"].PrefixPosition(), recipes => recipes
.AddClass("recipes").Id("recipes")
.AddClass("recipes")
.Id("recipes")
.Permission(StandardPermissions.SiteOwner)
.Action("Index", "Admin", "OrchardCore.Recipes")
.LocalNav()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using OrchardCore.Admin;
using OrchardCore.DisplayManagement.Notify;
Expand All @@ -30,9 +31,12 @@ public class AdminController : Controller
private readonly IRecipeExecutor _recipeExecutor;
private readonly IEnumerable<IRecipeEnvironmentProvider> _environmentProviders;
private readonly INotifier _notifier;
protected readonly IHtmlLocalizer H;
private readonly ILogger _logger;

protected readonly IHtmlLocalizer H;
protected readonly IStringLocalizer S;


public AdminController(
IShellHost shellHost,
ShellSettings shellSettings,
Expand All @@ -42,8 +46,9 @@ public AdminController(
IRecipeExecutor recipeExecutor,
IEnumerable<IRecipeEnvironmentProvider> environmentProviders,
INotifier notifier,
IHtmlLocalizer<AdminController> localizer,
ILogger<AdminController> logger)
ILogger<AdminController> logger,
IHtmlLocalizer<AdminController> htmlLocalizer,
IStringLocalizer<AdminController> stringLocalizer)
{
_shellHost = shellHost;
_shellSettings = shellSettings;
Expand All @@ -53,8 +58,9 @@ public AdminController(
_recipeExecutor = recipeExecutor;
_environmentProviders = environmentProviders;
_notifier = notifier;
H = localizer;
_logger = logger;
H = htmlLocalizer;
S = stringLocalizer;
}

[Admin("Recipes", "Recipes")]
Expand Down Expand Up @@ -105,13 +111,32 @@ public async Task<ActionResult> Execute(string basePath, string fileName)
var environment = new Dictionary<string, object>();
await _environmentProviders.OrderBy(x => x.Order).InvokeAsync((provider, env) => provider.PopulateEnvironmentAsync(env), environment, _logger);

var executionId = Guid.NewGuid().ToString("n");
try
{
var executionId = Guid.NewGuid().ToString("n");

await _recipeExecutor.ExecuteAsync(executionId, recipe, environment, CancellationToken.None);
await _recipeExecutor.ExecuteAsync(executionId, recipe, environment, CancellationToken.None);

await _shellHost.ReleaseShellContextAsync(_shellSettings);
await _shellHost.ReleaseShellContextAsync(_shellSettings);

await _notifier.SuccessAsync(H["The recipe '{0}' has been run successfully.", recipe.DisplayName]);
await _notifier.SuccessAsync(H["The recipe '{0}' has been run successfully.", recipe.DisplayName]);
}
catch (RecipeExecutionException e)
{
_logger.LogError(e, "Unable to import a recipe file.");

foreach (var entry in e.StepResult.Errors)
{
ModelState.AddModelError(string.Empty, string.Join(' ', entry.Value));

}
}
catch (Exception e)
{
_logger.LogError(e, "Unable to import a recipe file.");

ModelState.AddModelError(string.Empty, S["Unexpected error occurred while importing the recipe."]);
}

return RedirectToAction(nameof(Index));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
using OrchardCore.Recipes.Models;
using OrchardCore.Recipes.Services;

Expand All @@ -15,9 +16,14 @@ public class RecipesStep : IRecipeStepHandler
{
private readonly IEnumerable<IRecipeHarvester> _recipeHarvesters;

public RecipesStep(IEnumerable<IRecipeHarvester> recipeHarvesters)
protected readonly IStringLocalizer S;

public RecipesStep(
IEnumerable<IRecipeHarvester> recipeHarvesters,
IStringLocalizer<RecipesStep> stringLocalizer)
{
_recipeHarvesters = recipeHarvesters;
S = stringLocalizer;
}

public async Task ExecuteAsync(RecipeExecutionContext context)
Expand All @@ -37,7 +43,9 @@ public async Task ExecuteAsync(RecipeExecutionContext context)
{
if (!recipes.TryGetValue(recipe.Name, out var value))
{
throw new ArgumentException($"No recipe named '{recipe.Name}' was found.");
context.Errors.Add(S["No recipe named '{0}' was found.", recipe.Name]);

continue;
}

innerRecipes.Add(value);
Expand All @@ -54,6 +62,7 @@ private sealed class InternalStep
private sealed class InternalStepValue
{
public string ExecutionId { get; set; }

public string Name { get; set; }
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
using System.Collections.Generic;
using System.Text.Json.Nodes;

namespace OrchardCore.Recipes.Models
namespace OrchardCore.Recipes.Models;

public class RecipeExecutionContext
{
public class RecipeExecutionContext
{
public string ExecutionId { get; set; }
public object Environment { get; set; }
public string Name { get; set; }
public JsonObject Step { get; set; }
public RecipeDescriptor RecipeDescriptor { get; set; }
public IEnumerable<RecipeDescriptor> InnerRecipes { get; set; }
}
public string ExecutionId { get; set; }

public object Environment { get; set; }

public string Name { get; set; }

public JsonObject Step { get; set; }

public RecipeDescriptor RecipeDescriptor { get; set; }

public IEnumerable<RecipeDescriptor> InnerRecipes { get; set; }

public IList<string> Errors { get; } = [];
}
Loading

0 comments on commit 814f4f6

Please sign in to comment.