Skip to content

Commit 5bdc933

Browse files
CopilotMikeAlhayek
andauthored
Merge origin/main into ma/fix-recipe-steps
Co-authored-by: MikeAlhayek <24724371+MikeAlhayek@users.noreply.github.com>
2 parents 7f57607 + 619d27a commit 5bdc933

53 files changed

Lines changed: 2026 additions & 88 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CrestApps.OrchardCore.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
<Project Path="src/Modules/CrestApps.OrchardCore.Resources/CrestApps.OrchardCore.Resources.csproj" />
7878
<Project Path="src/Modules/CrestApps.OrchardCore.Roles/CrestApps.OrchardCore.Roles.csproj" />
7979
<Project Path="src/Modules/CrestApps.OrchardCore.SignalR/CrestApps.OrchardCore.SignalR.csproj" />
80+
<Project Path="src/Modules/CrestApps.OrchardCore.TimeZones/CrestApps.OrchardCore.TimeZones.csproj" />
8081
<Project Path="src/Modules/CrestApps.OrchardCore.Users/CrestApps.OrchardCore.Users.csproj" />
8182
</Folder>
8283
<Folder Name="/src/Targets/">

Directory.Packages.props

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
44
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
5-
<OrchardCoreVersion>[3.0.0-preview-19039, )</OrchardCoreVersion>
5+
<OrchardCoreVersion>[3.0.0-preview-19043, )</OrchardCoreVersion>
66
<CrestAppsCoreVersion>1.0.0-preview.121</CrestAppsCoreVersion>
77
<ModelContextProtocolVersion>1.4.0</ModelContextProtocolVersion>
88
</PropertyGroup>
@@ -16,6 +16,7 @@
1616
<PackageVersion Include="JsonSchema.Net" Version="8.0.5" />
1717
<PackageVersion Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00017" />
1818
<PackageVersion Include="Markdig" Version="1.2.0" />
19+
<PackageVersion Include="NodaTime" Version="3.3.2" />
1920
<PackageVersion Include="NLog" Version="6.1.3" />
2021
<PackageVersion Include="NLog.Web.AspNetCore" Version="6.1.3" />
2122
<PackageVersion Include="NuGet.Versioning" Version="7.6.0" />
@@ -153,9 +154,9 @@
153154
</ItemGroup>
154155
<ItemGroup>
155156
<!-- Aspire Packages -->
156-
<PackageVersion Include="Aspire.Hosting.AppHost" Version="13.4.2" />
157+
<PackageVersion Include="Aspire.Hosting.AppHost" Version="13.4.3" />
157158
<PackageVersion Include="Aspire.Hosting.Elasticsearch" Version="13.3.0" />
158-
<PackageVersion Include="Aspire.Hosting.Redis" Version="13.4.2" />
159+
<PackageVersion Include="Aspire.Hosting.Redis" Version="13.4.3" />
159160
<PackageVersion Include="CommunityToolkit.Aspire.Hosting.Ollama" Version="13.4.0" />
160161
</ItemGroup>
161162
<ItemGroup>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using Json.Schema;
2+
3+
namespace CrestApps.OrchardCore.Recipes.Core.Schemas;
4+
5+
/// <summary>
6+
/// Schema for the "TimeZoneMaps" recipe step.
7+
/// </summary>
8+
public sealed class TimeZoneMapsRecipeStep : RecipeStepSchemaBase
9+
{
10+
/// <inheritdoc />
11+
public override string Name => "TimeZoneMaps";
12+
13+
protected override JsonSchema CreateSchema()
14+
{
15+
var mapSchema = new JsonSchemaBuilder()
16+
.Type(SchemaValueType.Object)
17+
.Properties(
18+
("ItemId", new JsonSchemaBuilder().Type(SchemaValueType.String).Description("Optional unique identifier.")),
19+
("Name", new JsonSchemaBuilder().Type(SchemaValueType.String).Description("Unique friendly time zone name.")),
20+
("TimeZoneId", new JsonSchemaBuilder().Type(SchemaValueType.String).Description("IANA time zone identifier to store for this map.")),
21+
("OwnerId", new JsonSchemaBuilder().Type(SchemaValueType.String).Description("The user id of the user creating the entry. Leave it blank to use the current user's Id.")),
22+
("Author", new JsonSchemaBuilder().Type(SchemaValueType.String).Description("The username of the user creating the entry. Leave it blank to use the current username.")))
23+
.Required("Name", "TimeZoneId")
24+
.AdditionalProperties(true);
25+
26+
return new JsonSchemaBuilder()
27+
.Type(SchemaValueType.Object)
28+
.Properties(
29+
("name", new JsonSchemaBuilder().Type(SchemaValueType.String).Const("TimeZoneMaps")),
30+
("Maps", new JsonSchemaBuilder()
31+
.Type(SchemaValueType.Array)
32+
.Items(mapSchema)
33+
.MinItems(1)
34+
.Description("The time zone maps to create or update.")))
35+
.Required("name", "Maps")
36+
.AdditionalProperties(true)
37+
.Build();
38+
}
39+
}

src/CrestApps.Docs/docs/changelog/v2.0.0.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ Large parts of the reusable AI infrastructure are no longer implemented only ins
103103
- Omnichannel campaign, AI workflow direct-config, and content access control admin editors now use the current `ocat-*` admin theme field and checkbox layout classes instead of legacy `mb-3` and `form-label` markup, keeping labels, validation, and checkbox alignment consistent with the modern Orchard Core admin UI
104104
- AI memory, prompting, documents, and post-session admin editors now use the current `ocat-*` admin theme field and checkbox layout classes for settings rows, template selectors, attached-file editors, and post-session task headings, preserving existing client-side behavior while aligning with the modern Orchard Core admin UI
105105
- admin `.SummaryAdmin.cshtml` metadata badges that previously always showed `CreatedUtc` now prefer `ModifiedUtc` when available across the affected AI and Omnichannel catalog summaries, so admins see the latest meaningful update time
106+
- the new Time Zones admin list now shows the mapped `TimeZoneId` as a badge, renders the author through the shared `user-display-name` summary badge, and preserves `Author`, `OwnerId`, `CreatedUtc`, and `ModifiedUtc` metadata through recipe and deployment import/export
107+
- the Time Zones docs now clarify that the module overrides Orchard Core's `ITimeZoneSelectListProvider`, so Orchard time zone menus switch to the mapped friendly-name list when callers use that shared provider, and the seed recipe now stamps admin-owned audit metadata through shared recipe variables
106108
- Orchard AI deployment selection code and guidance now use `AIDeploymentPurpose` and the purpose-based deployment manager APIs instead of the obsolete `AIDeploymentType` helpers
107109
- Added `Vision` as a new deployment purpose; the default deployment settings UI now includes a Vision dropdown, and chat UIs pass a `visionEnabled` flag to the frontend when a vision deployment is available
108110
- AI deployment admin UI relabeled from "Type" to "Purpose" to align with the new `AIDeploymentPurpose` enum
@@ -119,6 +121,7 @@ Large parts of the reusable AI infrastructure are no longer implemented only ins
119121
- Content Transfer imports now commit inline status changes before their deferred background jobs run, use **Pending**, **Paused**, and **Deleting** import states in the admin list, surface **Resume import** and **Pause import** actions instead of the earlier cancel/process wording, and migrate older canceled import rows to the paused state
120122
- Omnichannel contact import and export now map the first contact-method bag entries to workbook `Email`, `Cell Phone`, and `Phone` columns, also read and write `DoNotCall`, `DoNotCallUtc`, `DoNotSms`, `DoNotSmsUtc`, `DoNotEmail`, `DoNotEmailUtc`, `DoNotChat`, and `DoNotChatUtc` on `OmnichannelContactPart`, advertise `true` and `false` for the `DoNot*` boolean columns, default duplicate-phone filtering to enabled, add skipped duplicate rows to the error export with a reason, batch-check duplicate phone numbers against both the import file and the existing Orchard contact records, fall back to legacy stored phone values when older tenants have not yet backfilled normalized-phone indexes, normalize non-E.164 import numbers before handing them to DNC registry providers, and can enforce national do-not-call registry checks through **Settings** -> **Import Content Settings**
121123
- Omnichannel contacts now store a `TimeZoneId` on `OmnichannelContactPart`; Content Transfer imports and exports include that column, missing values can be inferred from imported phone numbers through `IPhoneNumberService`, and the scheduled activities list can now filter by lead time zone while showing each contact's current local time with a tooltip for the full local date/time and IANA time zone id
124+
- Added a new `CrestApps.OrchardCore.TimeZones` module that manages friendly named time zone maps through admin UI, recipes, deployment plans, and a create-time seed migration recipe, and Omnichannel time zone selectors now read their available options from `ITimeZoneSelectListProvider` instead of Orchard Core's full raw IANA list
122125
- `OmnichannelContactPart` now supports part settings for **Require time zone** and opt-in rendering of the Do not SMS, Do not chat, and Do not email preference fields, with **Require time zone** and **Use Do not call** enabled by default
123126
- Omnichannel contact and communication-preference content-item indexes now subscribe to Orchard's default content-item collection instead of the custom omnichannel document collection, so publishing or updating a contact reliably refreshes its index rows
124127
- upgrade repairs now delete the empty legacy omnichannel-collection contact index tables created by earlier previews, recreate the correct default-collection tables, and reindex published contacts so activity phone, time-zone, and do-not-call filters keep working after upgrade

src/CrestApps.Docs/docs/intro.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ The AI modules add Orchard admin experiences and feature wiring on top of CrestA
4141
- **[Resources](modules/resources)**
4242
- **[Roles](modules/roles)**
4343
- **[SignalR](modules/signalr)**
44+
- **[Time Zones](modules/time-zones)**
4445
- **[Users](modules/users)**
4546

4647
### Omnichannel Communications

src/CrestApps.Docs/docs/modules/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ CrestApps provides a set of standard modules that enhance core Orchard Core CMS
2121
| [Resources](resources) | `CrestApps.OrchardCore.Resources` | Shared scripts and stylesheets |
2222
| [Roles](roles) | `CrestApps.OrchardCore.Roles` | Enhanced role management with RolePickerPart |
2323
| [SignalR](signalr) | `CrestApps.OrchardCore.SignalR` | Real-time communication via SignalR |
24+
| [Time Zones](time-zones) | `CrestApps.OrchardCore.TimeZones` | Friendly named time zone maps and grouped time zone selection |
2425
| [Users](users) | `CrestApps.OrchardCore.Users` | Enhanced user management with display names and avatars |
2526

2627
## Installation
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
---
2+
sidebar_label: Time Zones
3+
sidebar_position: 7
4+
title: Time Zones Feature
5+
description: Friendly named time zone maps and grouped time zone selection for Orchard Core.
6+
---
7+
8+
| | |
9+
| --- | --- |
10+
| **Feature Name** | Time Zones |
11+
| **Feature ID** | `CrestApps.OrchardCore.TimeZones` |
12+
13+
Provides friendly named time zone maps so editors can pick labels like `Eastern Time (US & Canada)` instead of scanning the full Orchard Core IANA time zone list.
14+
15+
## Overview
16+
17+
The module adds:
18+
19+
- an admin UI under **Tools -> Time Zones**
20+
- a catalog-backed store of unique named time zone maps
21+
- an `ITimeZoneSelectListProvider` override that replaces Orchard Core's default time zone select-list implementation
22+
- recipe import support through `TimeZoneMaps`
23+
- deployment export support for time zone maps
24+
- seeded starter mappings for common worldwide time zones
25+
26+
Because this module overrides Orchard Core's `ITimeZoneSelectListProvider`, enabling **Time Zones** changes the time zone menus Orchard Core renders anywhere that service is used. Consumers should resolve `ITimeZoneSelectListProvider` instead of building their own time zone list so the user always sees the mapped, human-friendly names.
27+
28+
Each map stores:
29+
30+
- a unique **Name** shown to editors
31+
- a **TimeZoneId** value stored in Orchard Core data
32+
- **Author**, **OwnerId**, **CreatedUtc**, and **ModifiedUtc** metadata for admin auditing
33+
34+
## Admin management
35+
36+
Enable the feature, then open **Configuration -> Time Zones**.
37+
38+
Create one map entry for each friendly label you want to expose. Names are unique and immutable after creation. The admin list shows the mapped `TimeZoneId`, the author display name, and the latest created or modified timestamp as badges so editors can scan changes quickly.
39+
40+
## Recipe support
41+
42+
Use the `TimeZoneMaps` step to create or update maps:
43+
44+
```json
45+
{
46+
"name": "TimeZoneMaps",
47+
"Maps": [
48+
{
49+
"Name": "Eastern Time (US & Canada)",
50+
"TimeZoneId": "America/New_York",
51+
"OwnerId": "[js: parameters('AdminUserId')]",
52+
"Author": "[js: parameters('AdminUsername')]"
53+
},
54+
{
55+
"Name": "India Standard Time",
56+
"TimeZoneId": "Asia/Kolkata",
57+
"OwnerId": "[js: parameters('AdminUserId')]",
58+
"Author": "[js: parameters('AdminUsername')]"
59+
}
60+
]
61+
}
62+
```
63+
64+
Recipe imports update existing entries by `ItemId` when provided, then fall back to the unique `Name`. The step also accepts `Author`, `OwnerId`, `CreatedUtc`, and `ModifiedUtc` so seeded or deployed entries can preserve audit metadata.
65+
66+
## Deployment support
67+
68+
When **OrchardCore.Deployment** is enabled, deployment plans can export all time zone maps or a selected subset. The exported plan uses the same `TimeZoneMaps` recipe step shape, so it can be imported directly into another tenant.
69+
70+
## Seeded starter maps
71+
72+
The initial migration runs an embedded partial recipe through Orchard Core's recipe executor and creates a starter set of common worldwide mappings. The seed recipe sets `OwnerId` from `parameters('AdminUserId')`, `Author` from `parameters('AdminUsername')`, and shares a single `utcNow()` value through recipe variables so all seeded entries keep consistent audit metadata. The starter mappings include:
73+
74+
- Pacific, Mountain, Central, Eastern, and Atlantic North American zones
75+
- UTC, Western European, Central European, and Eastern European zones
76+
- India, China, Japan, Gulf, Australia Eastern, and New Zealand zones
77+
78+
You can edit or delete these entries after the feature is enabled.

src/CrestApps.Docs/sidebars.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ const sidebars = {
105105
'modules/resources',
106106
'modules/roles',
107107
'modules/signalr',
108+
'modules/time-zones',
108109
'modules/users',
109110
],
110111
},

src/Modules/CrestApps.OrchardCore.Omnichannel.Managements/CrestApps.OrchardCore.Omnichannel.Managements.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<PackageReference Include="OrchardCore.Flows.Core" />
1919
<PackageReference Include="OrchardCore.Navigation.Core" />
2020
<PackageReference Include="OrchardCore.Sms.Abstractions" />
21+
<PackageReference Include="NodaTime" />
2122
</ItemGroup>
2223

2324
<ItemGroup>
@@ -37,6 +38,7 @@
3738
<ProjectReference Include="../CrestApps.OrchardCore.DncRegistry/CrestApps.OrchardCore.DncRegistry.csproj" PrivateAssets="none" />
3839
<ProjectReference Include="../CrestApps.OrchardCore.Omnichannel/CrestApps.OrchardCore.Omnichannel.csproj" PrivateAssets="none" />
3940
<ProjectReference Include="../CrestApps.OrchardCore.PhoneNumbers/CrestApps.OrchardCore.PhoneNumbers.csproj" PrivateAssets="none" />
41+
<ProjectReference Include="../CrestApps.OrchardCore.TimeZones/CrestApps.OrchardCore.TimeZones.csproj" PrivateAssets="none" />
4042
<ProjectReference Include="../CrestApps.OrchardCore.ContentFields/CrestApps.OrchardCore.ContentFields.csproj" PrivateAssets="none" />
4143
<ProjectReference Include="../CrestApps.OrchardCore.Users/CrestApps.OrchardCore.Users.csproj" PrivateAssets="none" />
4244
</ItemGroup>

src/Modules/CrestApps.OrchardCore.Omnichannel.Managements/Drivers/BulkManageActivityFilterDisplayDriver.cs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ internal sealed class BulkManageActivityFilterDisplayDriver : DisplayDriver<Bulk
2424
{
2525
private readonly LinkGenerator _linkGenerator;
2626
private readonly ISession _session;
27-
private readonly IClock _clock;
27+
private readonly ITimeZoneSelectListProvider _timeZoneSelectListProvider;
2828
private readonly ISubjectFlowSettingsService _subjectFlowSettingsService;
2929

3030
internal readonly IStringLocalizer S;
@@ -40,13 +40,13 @@ internal sealed class BulkManageActivityFilterDisplayDriver : DisplayDriver<Bulk
4040
public BulkManageActivityFilterDisplayDriver(
4141
LinkGenerator linkGenerator,
4242
ISession session,
43-
IClock clock,
43+
ITimeZoneSelectListProvider timeZoneSelectListProvider,
4444
ISubjectFlowSettingsService subjectFlowSettingsService,
4545
IStringLocalizer<BulkManageActivityFilterDisplayDriver> stringLocalizer)
4646
{
4747
_linkGenerator = linkGenerator;
4848
_session = session;
49-
_clock = clock;
49+
_timeZoneSelectListProvider = timeZoneSelectListProvider;
5050
_subjectFlowSettingsService = subjectFlowSettingsService;
5151
S = stringLocalizer;
5252
}
@@ -140,14 +140,11 @@ public override IDisplayResult Edit(BulkManageActivityFilter filter, BuildEditor
140140

141141
model.SubjectContentTypes = subjectContentTypes.OrderBy(x => x.Text);
142142

143-
var timeZones = new List<SelectListItem>();
144-
145-
foreach (var timeZone in _clock.GetTimeZones())
146-
{
147-
timeZones.Add(new SelectListItem(timeZone.TimeZoneId, timeZone.TimeZoneId));
148-
}
149-
150-
model.TimeZones = timeZones.OrderBy(x => x.Text);
143+
model.TimeZones = (await _timeZoneSelectListProvider.GetTimeZoneSelectListAsync())
144+
.Select(x => new SelectListItem(x.Value, x.Key)
145+
{
146+
Selected = model.TimeZoneIds?.Contains(x.Key, StringComparer.OrdinalIgnoreCase) == true,
147+
});
151148

152149
model.UserSearchEndpoint = _linkGenerator.GetPathByName("CrestApps.Users.Search", new { valueType = "userId" });
153150

0 commit comments

Comments
 (0)