Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Workflow - content for each task - allows looping through content items using a query or contentype #11799

Draft
wants to merge 26 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
fe6660c
Content for each task
PBMikeW Jun 2, 2022
d8b08c5
Update documentation
PBMikeW Jun 2, 2022
b0b69c8
Rename files / classes to fix module conventions, tidy
PBMikeW Jun 3, 2022
cd9c0f9
Fix typo
PBMikeW Jun 6, 2022
580bbdb
Fix getting data on each iteration, implement take.
PBMikeW Aug 22, 2022
bff3a24
Update NuGet.config
sebastienros Oct 13, 2022
c134d2a
Use latest translations
sebastienros Oct 13, 2022
700bf7b
OrchardCore.Translations.All 1.5.0
agriffard Oct 20, 2022
efb4049
Update NuGet.config
sebastienros Oct 20, 2022
9f89d27
Update doc
Skrypt Oct 21, 2022
da8ff45
doc
Skrypt Oct 23, 2022
37f5428
Merge branch 'main' into release/1.5
sebastienros Oct 25, 2022
ab8701a
Add documentation for YesSQL breaking change (#12681)
Skrypt Oct 25, 2022
3c54f3f
Merge remote-tracking branch 'upstream/release/1.5' into Workflow-Con…
PBMikeW Nov 4, 2022
664dcce
Merge remote-tracking branch 'upstream/main' into Workflow-ContentFor…
PBMikeW Nov 20, 2022
1706573
Tidied, added ability to use lucene or elastic query
PBMikeW Nov 21, 2022
687c774
Fix index iterate bug
PBMikeW Nov 21, 2022
e627681
Moved PublishedOnly into content type area, attempt to fix test error
PBMikeW Nov 22, 2022
d1c1014
Modify workflow to only allow queries if the feature is enabled, fixe…
PBMikeW Nov 22, 2022
c0ebabd
Remove test code
PBMikeW Nov 22, 2022
52b4c3a
Merge branch 'main' into Workflow-ContentForEach
Piedone Mar 13, 2024
31fd4aa
Merge branch 'main' into Workflow-ContentForEach
PBMikeW Mar 25, 2024
19bd127
Merge branch 'main' into Workflow-ContentForEach
PBMikeW Jul 24, 2024
aa6d7a9
Merge remote-tracking branch 'upstream/main' into Workflow-ContentFor…
PBMikeW Aug 2, 2024
c02772a
Tidy and simplify
PBMikeW Sep 10, 2024
7f959b5
Merge remote-tracking branch 'upstream/release/2.0' into Workflow-Con…
PBMikeW Sep 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@model OrchardCore.Workflows.ViewModels.ActivityViewModel<ContentForEachTask>

<header>
<h4><i class="fa fa-circle-o-notch"></i>@Model.Activity.GetTitleOrDefault(() => T["Content For Each"])</h4>
</header>
<em>@Model.Activity.ContentType</em>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@model ContentForEachTaskViewModel

<div class="form-group" asp-validation-class-for="ContentType">
<label asp-for="ContentType">@T["Content Type"]</label>
<input type="text" asp-for="ContentType" class="form-control" />
<span asp-validation-for="ContentType"></span>
<span class="hint">@T["The content type to loop over"]</span>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<h4 class="card-title"><i class="fa fa-circle-o-notch"></i>@T["Content For Each"]</h4>
<p>@T["Content for each task"]</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using Microsoft.Extensions.Localization;
using OrchardCore.Workflows.Abstractions.Models;
using OrchardCore.Workflows.Activities;
using OrchardCore.Workflows.Models;
using OrchardCore.ContentManagement;
using OrchardCore.ContentManagement.Records;
using YesSql;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using OrchardCore.ContentManagement.Workflows;

namespace OrchardCore.Contents.Workflows.Activities
{
public class ContentForEachTask : TaskActivity
{
readonly IStringLocalizer S;
private readonly ISession _session;
private readonly IContentManager _contentManager;

public ContentForEachTask(IContentManager contentManager, IStringLocalizer<RetrieveContentTask> localizer, ISession session)
{
S = localizer;
_session = session;
_contentManager = contentManager;
}
public override string Name => nameof(ContentForEachTask);

public override LocalizedString DisplayText => S["Content For Each Task"];

public override LocalizedString Category => S["Content"];

public override IEnumerable<Outcome> GetPossibleOutcomes(WorkflowExecutionContext workflowContext, ActivityContext activityContext)
{
return Outcomes(S["Iterate"], S["Done"]);
}
public override bool CanExecute(WorkflowExecutionContext workflowContext, ActivityContext activityContext)
{
return true;
}
public override async Task<ActivityExecutionResult> ExecuteAsync(WorkflowExecutionContext workflowContext, ActivityContext activityContext)
{
var contentItems = await _session
.Query<ContentItem, ContentItemIndex>(index => index.ContentType == ContentType && index.Published == true && index.Latest == true)
PBMikeW marked this conversation as resolved.
Show resolved Hide resolved
PBMikeW marked this conversation as resolved.
Show resolved Hide resolved
.ListAsync();
List<ContentItem> items = contentItems.ToList();
if (contentItems.Count() == 0)
PBMikeW marked this conversation as resolved.
Show resolved Hide resolved
{
throw new InvalidOperationException($"The '{nameof(ContentForEachTask)}' failed to evaluate the 'ContentType'.");
}

if (Index < contentItems.Count())
{
var contentItem = await _contentManager.LoadAsync(items[Index]);
PBMikeW marked this conversation as resolved.
Show resolved Hide resolved
var current = Current = contentItem;
workflowContext.CorrelationId = contentItem.ContentItemId;
workflowContext.Properties[ContentEventConstants.ContentItemInputKey] = contentItem;
workflowContext.LastResult = current;
Index++;
return Outcomes("Iterate");
}
else
{
Index = 0;
return Outcomes("Done");
}
}

/// <summary>
/// The current number of iterations executed.
/// </summary>
public int Index
{
get => GetProperty(() => 0);
set => SetProperty(value);
}

/// <summary>
/// The current iteration value.
/// </summary>
public object Current
{
get => GetProperty<object>();
set => SetProperty(value);
}

public string ContentType
{
get => GetProperty(() => string.Empty);
set => SetProperty(value);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using OrchardCore.Workflows.Display;
using OrchardCore.Contents.Workflows.ViewModels;
using OrchardCore.Contents.Workflows.Activities;

namespace OrchardCore.Contents.Workflows.Drivers
{


public class ContentForEachTaskDisplayDriver: ActivityDisplayDriver<ContentForEachTask, ContentForEachTaskViewModel>
{
protected override void EditActivity(ContentForEachTask activity, ContentForEachTaskViewModel model)
{
model.ContentType = activity.ContentType;
}

protected override void UpdateActivity(ContentForEachTaskViewModel model, ContentForEachTask activity)
{
activity.ContentType = model.ContentType;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public override void ConfigureServices(IServiceCollection services)
services.AddActivity<CreateContentTask, CreateContentTaskDisplayDriver>();
services.AddActivity<RetrieveContentTask, RetrieveContentTaskDisplayDriver>();
services.AddActivity<UpdateContentTask, UpdateContentTaskDisplayDriver>();

services.AddActivity<ContentForEachTask, ContentForEachTaskDisplayDriver>();
services.AddScoped<IContentHandler, ContentsHandler>();
services.AddScoped<IWorkflowValueSerializer, ContentItemSerializer>();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using OrchardCore.Environment.Shell;
using OrchardCore.Workflows.Abstractions.Models;
using OrchardCore.Workflows.Activities;
using OrchardCore.Workflows.Services;
using OrchardCore.Workflows.Display;
using OrchardCore.Workflows.Models;
using OrchardCore.ContentManagement;
using OrchardCore.ContentManagement.Workflows;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using OrchardCore.Contents.Workflows.Activities;
using System.ComponentModel.DataAnnotations;

namespace OrchardCore.Contents.Workflows.ViewModels
{

public class ContentForEachTaskViewModel
{
[Required]
public string ContentType { get; set; }
}

}
6 changes: 6 additions & 0 deletions src/docs/reference/modules/Workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ Although many activities support multiple outcomes, they typically return only o
For example, the _Send Email_ activity has two possible outcomes: "Done" and "Failed".
When the email was sent successfully, it yields "Done" as the outcome, and "Failed" otherwise.

For activities that have an iterator outcome e.g _Content For Each_, return the outcome of the next activity back to the irrerate task for the irritation to continue either through the done outcome or via a fork.
PBMikeW marked this conversation as resolved.
Show resolved Hide resolved
If forking make sure that the loop output is after the action output.

![Sample content for each ](docs/sample-content-for-each.png)

### Transition

A transition is the connection between the outcome of one activity to another activity. Transitions are created using drag & drop operations in the workflow editor.
Expand Down Expand Up @@ -241,6 +246,7 @@ The following activities are available with any default Orchard installation:
| Create Content | Task | Create a content item. |
| Delete Content | Task | Delete a content item. |
| Publish Content | Task | Publish a content item. |
| For Content Type | Task | Loop through a content type. |
**User** | * | * | * |
| ValidateUser | Task | Used to check if the user is logged in and has the specified role(s). |

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.