mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
Added RecipesStep.
This recipe steps enables the execution of other recipes within the same ExecutionId.
This commit is contained in:
@@ -94,7 +94,7 @@ Features:
|
||||
Enumerable.Empty<ShellParameter>());
|
||||
|
||||
var moduleStep = _container.Resolve<ModuleStep>();
|
||||
var recipeExecutionContext = new RecipeExecutionContext {RecipeStep = new RecipeStep { Name = "Module", Step = new XElement("SuperWiki") } };
|
||||
var recipeExecutionContext = new RecipeExecutionContext {RecipeStep = new RecipeStep( recipeName: "Test", name: "Module", step: new XElement("SuperWiki")) };
|
||||
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("packageId", "Orchard.Module.SuperWiki"));
|
||||
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("repository", "test"));
|
||||
|
||||
@@ -120,7 +120,7 @@ Features:
|
||||
");
|
||||
|
||||
var moduleStep = _container.Resolve<ModuleStep>();
|
||||
var recipeContext = new RecipeContext { RecipeStep = new RecipeStep { Name = "Module", Step = new XElement("SuperWiki") } };
|
||||
var recipeContext = new RecipeContext { RecipeStep = new RecipeStep(recipeName: "Test", name: "Module", step: new XElement("SuperWiki")) };
|
||||
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = recipeContext.RecipeStep };
|
||||
recipeContext.RecipeStep.Step.Add(new XAttribute("repository", "test"));
|
||||
|
||||
@@ -145,7 +145,7 @@ Features:
|
||||
});
|
||||
|
||||
var moduleStep = _container.Resolve<ModuleStep>();
|
||||
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = new RecipeStep { Name = "Module", Step = new XElement("SuperWiki") } };
|
||||
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = new RecipeStep(recipeName: "Test", name: "Module", step: new XElement("SuperWiki")) };
|
||||
|
||||
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("packageId", "Orchard.Module.SuperWiki"));
|
||||
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("repository", "test"));
|
||||
|
@@ -25,7 +25,7 @@ namespace Orchard.Tests.Modules.Recipes.RecipeHandlers {
|
||||
var fakeRecipeStep = _container.Resolve<StubRecipeExecutionStep>();
|
||||
|
||||
var context = new RecipeContext {
|
||||
RecipeStep = new RecipeStep { Name = "FakeRecipeStep", Step = new XElement("FakeRecipeStep")},
|
||||
RecipeStep = new RecipeStep (recipeName: "FakeRecipe", name: "FakeRecipeStep", step: new XElement("FakeRecipeStep")),
|
||||
ExecutionId = "12345"
|
||||
};
|
||||
|
||||
|
@@ -100,7 +100,7 @@ Features:
|
||||
Enumerable.Empty<ShellParameter>());
|
||||
|
||||
var themeStep = _container.Resolve<ThemeStep>();
|
||||
var recipeExecutionContext = new RecipeExecutionContext {RecipeStep = new RecipeStep { Name = "Theme", Step = new XElement("SuperWiki") } };
|
||||
var recipeExecutionContext = new RecipeExecutionContext {RecipeStep = new RecipeStep (recipeName: "Test", name: "Theme", step: new XElement("SuperWiki")) };
|
||||
|
||||
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("packageId", "Orchard.Theme.SuperWiki"));
|
||||
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("repository", "test"));
|
||||
@@ -135,7 +135,7 @@ Features:
|
||||
");
|
||||
|
||||
var themeStep = _container.Resolve<ThemeStep>();
|
||||
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = new RecipeStep { Name = "Theme", Step = new XElement("SuperWiki") } };
|
||||
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = new RecipeStep(recipeName: "Test", name: "Theme", step: new XElement("SuperWiki")) };
|
||||
|
||||
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("repository", "test"));
|
||||
Assert.Throws(typeof (InvalidOperationException), () => themeStep.Execute(recipeExecutionContext));
|
||||
@@ -159,7 +159,7 @@ Features:
|
||||
});
|
||||
|
||||
var themeStep = _container.Resolve<ThemeStep>();
|
||||
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = new RecipeStep { Name = "Theme", Step = new XElement("SuperWiki") } };
|
||||
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = new RecipeStep(recipeName: "Test", name: "Theme", step: new XElement("SuperWiki")) };
|
||||
|
||||
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("packageId", "Orchard.Theme.SuperWiki"));
|
||||
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("repository", "test"));
|
||||
|
@@ -16,6 +16,7 @@ else {
|
||||
<table class="items" style="width: auto;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>@T("Recipe")</th>
|
||||
<th>@T("Step")</th>
|
||||
<th>@T("Executed")</th>
|
||||
<th>@T("Result")</th>
|
||||
@@ -25,6 +26,7 @@ else {
|
||||
<tbody>
|
||||
@foreach (var step in Model.Result.Steps) {
|
||||
<tr>
|
||||
<td>@(!String.IsNullOrWhiteSpace(step.RecipeName) ? step.RecipeName : T("Untitled").ToString())</td>
|
||||
<td>@step.StepName</td>
|
||||
<td>@step.IsCompleted</td>
|
||||
<td>@if (step.IsSuccessful) { @T("Successful") } else if (step.IsCompleted) { <strong>@T("Failed")</strong> }</td>
|
||||
|
@@ -253,11 +253,12 @@ namespace Orchard.Packaging.Controllers {
|
||||
// Enable the features and its dependencies using recipes, so that they are run after the module's recipes
|
||||
|
||||
var recipe = new Recipe {
|
||||
Name = "Test",
|
||||
RecipeSteps = featureIds.Select(
|
||||
x => new RecipeStep {
|
||||
Name = "Feature",
|
||||
Step = new XElement("Feature", new XAttribute("enable", x))
|
||||
})
|
||||
x => new RecipeStep(
|
||||
recipeName: "Test",
|
||||
name: "Feature",
|
||||
step: new XElement("Feature", new XAttribute("enable", x))))
|
||||
};
|
||||
|
||||
_recipeManager.Execute(recipe);
|
||||
|
@@ -91,7 +91,7 @@ namespace Orchard.Recipes.Commands {
|
||||
Context.Output.WriteLine(T(" Successful: {0}", result.IsSuccessful));
|
||||
|
||||
foreach (var step in result.Steps) {
|
||||
Context.Output.WriteLine(T(" Step: {0}", step.StepName));
|
||||
Context.Output.WriteLine(T(" Step: {0} ({1})", step.StepName, step.RecipeName));
|
||||
Context.Output.WriteLine(T(" Completed: {0}", step.IsCompleted));
|
||||
Context.Output.WriteLine(T(" Successful: {0}", step.IsSuccessful));
|
||||
if (!String.IsNullOrEmpty(step.ErrorMessage))
|
||||
|
@@ -6,10 +6,11 @@ namespace Orchard.Recipes {
|
||||
SchemaBuilder.CreateTable("RecipeStepResultRecord", table => table
|
||||
.Column<int>("Id", c => c.PrimaryKey().Identity())
|
||||
.Column<string>("ExecutionId", c => c.WithLength(128).NotNull())
|
||||
.Column<string>("RecipeName", c => c.WithLength(256))
|
||||
.Column<string>("StepName", c => c.WithLength(256).NotNull())
|
||||
.Column<bool>("IsCompleted", c => c.NotNull())
|
||||
.Column<bool>("IsSuccessful", c => c.NotNull())
|
||||
.Column<string>("ErrorMessage", c => c.Unlimited().Nullable())
|
||||
.Column<string>("ErrorMessage", c => c.Unlimited())
|
||||
);
|
||||
|
||||
SchemaBuilder.AlterTable("RecipeStepResultRecord", table => {
|
||||
|
@@ -2,6 +2,7 @@
|
||||
public class RecipeStepResultRecord {
|
||||
public virtual int Id { get; set; }
|
||||
public virtual string ExecutionId { get; set; }
|
||||
public virtual string RecipeName { get; set; }
|
||||
public virtual string StepName { get; set; }
|
||||
public virtual bool IsCompleted { get; set; }
|
||||
public virtual bool IsSuccessful { get; set; }
|
||||
|
@@ -55,6 +55,9 @@
|
||||
<HintPath>..\..\..\..\lib\log4net\log4net.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="NHibernate">
|
||||
<HintPath>..\..\..\..\lib\nhibernate\NHibernate.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="System.Web.ApplicationServices" />
|
||||
@@ -89,6 +92,7 @@
|
||||
<Compile Include="Providers\Builders\RecipeMetadataStep.cs" />
|
||||
<Compile Include="Providers\Builders\SettingsStep.cs" />
|
||||
<Compile Include="Providers\Executors\CommandStep.cs" />
|
||||
<Compile Include="Providers\Executors\RecipesStep.cs" />
|
||||
<Compile Include="ViewModels\ContentExecutionStepViewModel.cs" />
|
||||
<Compile Include="Providers\Executors\ContentStep.cs" />
|
||||
<Compile Include="Providers\Executors\ContentDefinitionStep.cs" />
|
||||
|
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NHibernate;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Data;
|
||||
using Orchard.Logging;
|
||||
using Orchard.Recipes.Models;
|
||||
using Orchard.Recipes.Services;
|
||||
|
||||
namespace Orchard.Recipes.Providers.Executors {
|
||||
public class RecipesStep : RecipeExecutionStep {
|
||||
private readonly IRecipeHarvester _recipeHarvester;
|
||||
private readonly IRecipeStepQueue _recipeStepQueue;
|
||||
private readonly IRepository<RecipeStepResultRecord> _recipeStepResultRecordRepository;
|
||||
private readonly ISessionLocator _sessionLocator;
|
||||
|
||||
public RecipesStep(
|
||||
IRecipeHarvester recipeHarvester,
|
||||
IRecipeStepQueue recipeStepQueue,
|
||||
IRepository<RecipeStepResultRecord> recipeStepResultRecordRepository,
|
||||
ISessionLocator sessionLocator) {
|
||||
|
||||
_recipeHarvester = recipeHarvester;
|
||||
_recipeStepQueue = recipeStepQueue;
|
||||
_recipeStepResultRecordRepository = recipeStepResultRecordRepository;
|
||||
_sessionLocator = sessionLocator;
|
||||
}
|
||||
|
||||
public override string Name { get { return "Recipes"; } }
|
||||
|
||||
/*
|
||||
<Recipes>
|
||||
<Recipe ExtensionId="Orchard.Setup" Name="Core" />
|
||||
</Recipes>
|
||||
*/
|
||||
public override void Execute(RecipeExecutionContext context) {
|
||||
var recipeElements = context.RecipeStep.Step.Elements();
|
||||
var recipesDictionary = new Dictionary<string, IDictionary<string, Recipe>>();
|
||||
var session = _sessionLocator.For(typeof(RecipeStepResultRecord));
|
||||
|
||||
foreach (var recipeElement in recipeElements) {
|
||||
var extensionId = recipeElement.Attr("ExtensionId");
|
||||
var recipeName = recipeElement.Attr("Name");
|
||||
var recipes = recipesDictionary.ContainsKey(extensionId) ? recipesDictionary[extensionId] : default(IDictionary<string, Recipe>);
|
||||
|
||||
if (recipes == null) {
|
||||
recipes = recipesDictionary[extensionId] = HarvestRecipes(extensionId);
|
||||
}
|
||||
|
||||
var recipe = recipes.ContainsKey(recipeName) ? recipes[recipeName] : default(Recipe);
|
||||
|
||||
if (recipe == null) {
|
||||
Logger.Error(String.Format("No recipe named {0} was found for extension {1}", recipeName, extensionId));
|
||||
continue;
|
||||
}
|
||||
|
||||
EnqueueRecipe(session, context.ExecutionId, recipe);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnqueueRecipe(ISession session, string executionId, Recipe recipe) {
|
||||
foreach (var recipeStep in recipe.RecipeSteps) {
|
||||
_recipeStepQueue.Enqueue(executionId, recipeStep);
|
||||
_recipeStepResultRecordRepository.Create(new RecipeStepResultRecord {
|
||||
ExecutionId = executionId,
|
||||
RecipeName = recipe.Name,
|
||||
StepName = recipeStep.Name
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private IDictionary<string, Recipe> HarvestRecipes(string extensionId) {
|
||||
return _recipeHarvester.HarvestRecipes(extensionId).ToDictionary(x => x.Name);
|
||||
}
|
||||
}
|
||||
}
|
@@ -45,8 +45,9 @@ namespace Orchard.Recipes.Services {
|
||||
|
||||
foreach (var recipeStep in recipe.RecipeSteps) {
|
||||
_recipeStepQueue.Enqueue(executionId, recipeStep);
|
||||
_recipeStepResultRecordRepository.Create(new RecipeStepResultRecord() {
|
||||
_recipeStepResultRecordRepository.Create(new RecipeStepResultRecord {
|
||||
ExecutionId = executionId,
|
||||
RecipeName = recipe.Name,
|
||||
StepName = recipeStep.Name
|
||||
});
|
||||
}
|
||||
|
@@ -52,7 +52,7 @@ namespace Orchard.Recipes.Services {
|
||||
}
|
||||
// Recipe step.
|
||||
else {
|
||||
var recipeStep = new RecipeStep { Name = element.Name.LocalName, Step = element };
|
||||
var recipeStep = new RecipeStep(recipeName: recipe.Name, name: element.Name.LocalName, step: element );
|
||||
recipeSteps.Add(recipeStep);
|
||||
}
|
||||
}
|
||||
|
@@ -26,7 +26,8 @@ namespace Orchard.Recipes.Services {
|
||||
ExecutionId = executionId,
|
||||
Steps =
|
||||
from record in records
|
||||
select new RecipeStepResult() {
|
||||
select new RecipeStepResult {
|
||||
RecipeName = record.RecipeName,
|
||||
StepName = record.StepName,
|
||||
IsCompleted = record.IsCompleted,
|
||||
IsSuccessful = record.IsSuccessful,
|
||||
|
@@ -29,7 +29,7 @@ namespace Orchard.Recipes.Services {
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
public ILogger Logger;
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public void ScheduleWork(string executionId) {
|
||||
var shellDescriptor = _shellDescriptorManager.GetShellDescriptor();
|
||||
|
@@ -44,11 +44,11 @@ namespace Orchard.Recipes.Services {
|
||||
recipeHandler.ExecuteRecipeStep(recipeContext);
|
||||
}
|
||||
|
||||
UpdateStepResultRecord(executionId, nextRecipeStep.Name, isSuccessful: true);
|
||||
UpdateStepResultRecord(executionId, nextRecipeStep.RecipeName, nextRecipeStep.Name, isSuccessful: true);
|
||||
_recipeExecuteEventHandler.RecipeStepExecuted(executionId, recipeContext);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
UpdateStepResultRecord(executionId, nextRecipeStep.Name, isSuccessful: false, errorMessage: ex.Message);
|
||||
UpdateStepResultRecord(executionId, nextRecipeStep.RecipeName, nextRecipeStep.Name, isSuccessful: false, errorMessage: ex.Message);
|
||||
Logger.Error(ex, "Recipe execution failed because the step '{0}' failed.", nextRecipeStep.Name);
|
||||
while (_recipeStepQueue.Dequeue(executionId) != null);
|
||||
var message = T("Recipe execution with ID {0} failed because the step '{1}' failed to execute. The following exception was thrown:\n{2}\nRefer to the error logs for more information.", executionId, nextRecipeStep.Name, ex.Message);
|
||||
@@ -65,12 +65,15 @@ namespace Orchard.Recipes.Services {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdateStepResultRecord(string executionId, string stepName, bool isSuccessful, string errorMessage = null) {
|
||||
private void UpdateStepResultRecord(string executionId, string recipeName, string stepName, bool isSuccessful, string errorMessage = null) {
|
||||
var query =
|
||||
from record in _recipeStepResultRecordRepository.Table
|
||||
where record.ExecutionId == executionId && record.StepName == stepName
|
||||
select record;
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(recipeName))
|
||||
query = from record in query where record.RecipeName == recipeName select record;
|
||||
|
||||
var stepResultRecord = query.Single();
|
||||
|
||||
stepResultRecord.IsCompleted = true;
|
||||
|
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.FileSystems.AppData;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Logging;
|
||||
@@ -25,6 +26,7 @@ namespace Orchard.Recipes.Services {
|
||||
public void Enqueue(string executionId, RecipeStep step) {
|
||||
Logger.Information("Enqueuing recipe step '{0}'.", step.Name);
|
||||
var recipeStepElement = new XElement("RecipeStep");
|
||||
recipeStepElement.Attr("RecipeName", step.RecipeName);
|
||||
recipeStepElement.Add(new XElement("Name", step.Name));
|
||||
recipeStepElement.Add(step.Step);
|
||||
|
||||
@@ -52,11 +54,9 @@ namespace Orchard.Recipes.Services {
|
||||
// string to xelement
|
||||
var stepElement = XElement.Parse(_appDataFolder.ReadFile(stepPath));
|
||||
var stepName = stepElement.Element("Name").Value;
|
||||
var recipeName = stepElement.Attr("RecipeName");
|
||||
Logger.Information("Dequeuing recipe step '{0}'.", stepName);
|
||||
recipeStep = new RecipeStep {
|
||||
Name = stepName,
|
||||
Step = stepElement.Element(stepName)
|
||||
};
|
||||
recipeStep = new RecipeStep(recipeName: recipeName, name: stepName, step: stepElement.Element(stepName));
|
||||
_appDataFolder.DeleteFile(stepPath);
|
||||
}
|
||||
|
||||
|
@@ -2,7 +2,14 @@
|
||||
|
||||
namespace Orchard.Recipes.Models {
|
||||
public class RecipeStep {
|
||||
public string Name { get; set; }
|
||||
public XElement Step { get; set; }
|
||||
public RecipeStep(string recipeName, string name, XElement step) {
|
||||
RecipeName = recipeName;
|
||||
Name = name;
|
||||
Step = step;
|
||||
}
|
||||
|
||||
public string RecipeName { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public XElement Step { get; private set; }
|
||||
}
|
||||
}
|
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Orchard.Recipes.Models {
|
||||
public class RecipeStepResult {
|
||||
public string RecipeName { get; set; }
|
||||
public string StepName { get; set; }
|
||||
public bool IsCompleted { get; set; }
|
||||
public bool IsSuccessful { get; set; }
|
||||
|
Reference in New Issue
Block a user