I am using Relations extensively in one of our websites for handling i18n. The problem is that Relations are not being handled by uSync.
What does it take to extend uSync in order to handle Relations?
I looked up uSync.Core and I suspect that some core classes would have to be modified (e.g. uSyncContext). Any guidelines or suggestions?
Most things usync export have two elements, a Serializer and a Handler.
The serializer is the thing responsible for getting the settings in and out of umbraco - Some of the seralizers are more complex than others - but the Domain Serializer is probably a good example of how they might work
*Generally Serializers live in uSync.Core.
Handlers manage the IO for uSync - reading and writing things from disk, and acting on the events that are triggered in umbraco (like when you save a datatype). the Language Handler probably shows the simpliest way this works. Handlers usually live in uSync.BackOffice or uSync.ContentEdition
Relations could be added into this structure, or indeed they could be added as an extension, (there is no reason why the serializer/handlers have to be in the core or backoffice projects) - i suppose the real question i've had is where relations sit - most of the time they are content relations (although they can be something else) - So i would suppose relations would be part of ContentEdition?
Since Relations could relate more than Content nodes, why not a separate package?
Anyway, I will have to look more closely at your code to understand the uSync api first. I hope to find the time to write a minimal extension for this.
Looks like we are in the same boat, we'd like to use uSync on an i18n website for handling relations between localized pages.
I was wondering if you managed to make a head start on this by any chance. We'd be glad to chip in and complete the feature together if you are still up for this.
For Umbraco 8: Managed to do this by copying the Macro Handler, Macro Serializer, Macro Tracker and Macro Checker
Create following files:
RelationTypeHandler
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
using uSync8.BackOffice;
using uSync8.BackOffice.Configuration;
using uSync8.BackOffice.Services;
using uSync8.BackOffice.SyncHandlers;
using uSync8.Core;
using uSync8.Core.Dependency;
using uSync8.Core.Extensions;
using uSync8.Core.Serialization;
using uSync8.Core.Tracking;
using static Umbraco.Core.Constants;
namespace CustomNameSpace
{
[SyncHandler("relationTypeHandler", "Relation Types", "RelationTypes", uSyncBackOfficeConstants.Priorites.MemberTypes,
Icon = "icon-diagonal-arrow-alt", EntityType = UdiEntityType.RelationType, IsTwoPass = true)]
public class RelationTypeHandler : SyncHandlerBase<IRelationType, IRelationService>, ISyncExtendedHandler
{
private readonly IRelationService relationService;
public RelationTypeHandler(
IEntityService entityService,
IProfilingLogger logger,
IRelationService relationService,
ISyncSerializer<IRelationType> serializer,
ISyncTracker<IRelationType> tracker,
AppCaches appCaches,
ISyncDependencyChecker<IRelationType> checker,
SyncFileService syncFileService)
: base(entityService, logger, serializer, tracker, appCaches, checker, syncFileService)
{
this.relationService = relationService;
}
/// <summary>
/// overrider the default export, because macros, don't exist as an object type???
/// </summary>
public override IEnumerable<uSyncAction> ExportAll(int parent, string folder, HandlerSettings config, SyncUpdateCallback callback)
{
// we clean the folder out on an export all.
syncFileService.CleanFolder(folder);
var actions = new List<uSyncAction>();
var items = relationService.GetAllRelationTypes().ToList();
int count = 0;
foreach (var item in items)
{
count++;
callback?.Invoke(item.Name, count, items.Count);
actions.AddRange(Export(item, folder, config));
}
return actions;
}
protected override IRelationType GetFromService(int id)
=> relationService.GetRelationTypeById(id);
// not sure we can trust macro guids in the path just yet.
protected override string GetItemPath(IRelationType item, bool useGuid, bool isFlat)
{
if (useGuid) return item.Key.ToString();
return item.Alias.ToSafeAlias();
}
protected override void InitializeEvents(HandlerSettings settings)
{
RelationService.SavedRelationType += EventSavedItem;
RelationService.DeletedRelationType += EventDeletedItem;
}
protected override IRelationType GetFromService(Guid key)
{
return relationService.GetAllRelationTypes().FirstOrDefault(rt => rt.Key == key);
}
protected override IRelationType GetFromService(string alias)
=> relationService.GetRelationTypeByAlias(alias);
protected override void DeleteViaService(IRelationType item)
=> relationService.Delete(item);
protected override string GetItemName(IRelationType item)
=> item.Name;
protected override string GetItemAlias(IRelationType item)
=> item.Alias;
protected override IEnumerable<IEntity> GetChildItems(int parent)
{
if (parent == -1)
{
return relationService.GetAllRelationTypes().Where(x => x is IEntity)
.Select(x => x as IEntity);
}
return Enumerable.Empty<IEntity>();
}
}
}
RelationTypeSerializer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using uSync8.Core;
using uSync8.Core.Extensions;
using uSync8.Core.Models;
using uSync8.Core.Serialization;
namespace CustomNameSpace
{
[SyncSerializer("EE5439FF-37B5-4C2C-A396-233FE0563825", "RelationTypeSerializer", "RelationType")]
public class RelationTypeSerializer: SyncSerializerBase<IRelationType>, ISyncSerializer<IRelationType>
{
private readonly IRelationService relationTypeService;
public RelationTypeSerializer(
IEntityService entityService, ILogger logger,
IRelationService relationTypeService)
: base(entityService, logger)
{
this.relationTypeService = relationTypeService;
}
protected override SyncAttempt<IRelationType> DeserializeCore(XElement node)
{
var changes = new List<uSyncChange>();
if (node.Element("Name") == null)
throw new ArgumentNullException("XML missing Name parameter");
var item = default(IRelationType);
var key = node.GetKey();
var alias = node.GetAlias();
var name = node.Element("Name").ValueOrDefault(string.Empty);
var realtionType = node.Element("RelationType").ValueOrDefault(string.Empty);
logger.Debug<RelationTypeSerializer>("Relation Type by Alias [{0}]", key);
item = relationTypeService.GetRelationTypeByAlias(alias);
/*
if (item == null)
{
logger.Debug<RelationTypeSerializer>("Relation Type by Key [{0}]", key);
item = relationTypeService.GetRelationTypeById(key);
}
*/
if (item == null)
{
logger.Debug<RelationTypeSerializer>("Creating New [{0}]", key);
item = new RelationType(Guid.Empty, Guid.Empty, alias, name);
changes.Add(uSyncChange.Create(alias, name, "New RelationType"));
}
item.Key = key;
item.Name = name;
item.Alias = alias;
item.ParentObjectType = node.Element("parent").ValueOrDefault(Guid.Empty);
item.ChildObjectType = node.Element("child").ValueOrDefault(Guid.Empty);
item.IsBidirectional = node.Element("direction").ValueOrDefault(false);
var attempt = SyncAttempt<IRelationType>.Succeed(item.Name, item, ChangeType.Import);
if (changes.Any())
attempt.Details = changes;
return attempt;
}
private void RemoveOrphanProperties(IRelationType item, XElement properties)
{
var removalKeys = new List<string>();
}
protected override SyncAttempt<XElement> SerializeCore(IRelationType item)
{
var node = this.InitializeBaseNode(item, item.Alias);
node.Add(new XElement("Name", item.Name));
node.Add(new XElement("parent", item.ParentObjectType));
node.Add(new XElement("child", item.ChildObjectType));
node.Add(new XElement("direction", item.IsBidirectional));
return SyncAttempt<XElement>.SucceedIf(
node != null,
item.Name,
node,
typeof(IRelationType),
ChangeType.Export);
}
protected override IRelationType FindItem(string alias)
=> relationTypeService.GetRelationTypeByAlias(alias);
protected override void SaveItem(IRelationType item)
=> relationTypeService.Save(item);
protected override void DeleteItem(IRelationType item)
=> relationTypeService.Delete(item);
protected override string ItemAlias(IRelationType item)
=> item.Alias;
protected override IRelationType FindItem(Guid key)
{
return relationTypeService.GetAllRelationTypes().FirstOrDefault(rt => rt.Key == key);
}
}
}
RelationTypeTracker
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Core.Models;
using uSync8.Core.Serialization;
using uSync8.Core.Tracking;
namespace CustomNamespace
{
public class RelationTypeTracker : SyncBaseTracker<IRelationType>, ISyncTracker<IRelationType>
{
public RelationTypeTracker(ISyncSerializer<IRelationType> serializer) : base(serializer)
{
}
protected override TrackedItem TrackChanges()
{
return new TrackedItem(serializer.ItemType, true)
{
Children = new List<TrackedItem>()
{
new TrackedItem("Name", "/Name", true),
new TrackedItem("parent", "/parent", true),
new TrackedItem("child", "/child", true),
new TrackedItem("direction", "/direction", true)
}
};
}
}
}
RelationTypeChecker
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Core.Models;
using uSync8.Core.Dependency;
namespace CustomNameSpace
{
public class RelationTypeChecker : ISyncDependencyChecker<IRelationType>
{
public UmbracoObjectTypes ObjectType => UmbracoObjectTypes.Unknown;
public IEnumerable<uSyncDependency> GetDependencies(IRelationType item, DependencyFlags flags)
{
var dependencies = new List<uSyncDependency>
{
new uSyncDependency()
{
Name = item.Name,
Udi = item.GetUdi(),
Order = DependencyOrders.Media,
Flags = flags
}
};
return dependencies;
}
}
}
Hi all. I'm wondering if there's any thoughts on including this RelationTypes handler in uSync, whether it's already on the roadmap - or part of another package (that I'm not aware of)?
It's the first time that I'm using RelationTypes and uSync on the same project, so interested to see how this pans out.
In terms of timescales we are currently working out if we are waiting for Umbraco 8.7 - because we might want to also squish all the block editor stuff in too.
Thanks Kevin! I did have a search around your uSync repos, but didn't spot the branch/commit (probably because GitHub search only works in the main/default branch).
Looking forward to v8.7!
I'll have a think about my use cases, see how I'd expect it work, etc - and feed back.
Extending uSync to handle Relations and RelationTypes
I am using Relations extensively in one of our websites for handling i18n. The problem is that Relations are not being handled by uSync.
What does it take to extend uSync in order to handle Relations? I looked up uSync.Core and I suspect that some core classes would have to be modified (e.g. uSyncContext). Any guidelines or suggestions?
Thanks Dimitris
Hi
Yes it could be extended to handle relations.
Most things usync export have two elements, a Serializer and a Handler.
The serializer is the thing responsible for getting the settings in and out of umbraco - Some of the seralizers are more complex than others - but the Domain Serializer is probably a good example of how they might work *Generally Serializers live in uSync.Core.
Handlers manage the IO for uSync - reading and writing things from disk, and acting on the events that are triggered in umbraco (like when you save a datatype). the Language Handler probably shows the simpliest way this works. Handlers usually live in uSync.BackOffice or uSync.ContentEdition
Relations could be added into this structure, or indeed they could be added as an extension, (there is no reason why the serializer/handlers have to be in the core or backoffice projects) - i suppose the real question i've had is where relations sit - most of the time they are content relations (although they can be something else) - So i would suppose relations would be part of ContentEdition?
Thanks,
Since Relations could relate more than Content nodes, why not a separate package? Anyway, I will have to look more closely at your code to understand the uSync api first. I hope to find the time to write a minimal extension for this.
Thank you for your fantastic work.
Dear Dimitris,
Looks like we are in the same boat, we'd like to use uSync on an i18n website for handling relations between localized pages.
I was wondering if you managed to make a head start on this by any chance. We'd be glad to chip in and complete the feature together if you are still up for this.
Thanks Aron
Hi Kevin, I would just like to have the relation types managed by usync so that I do not have to create them manually on each environment.
Could you provide a sample of what I need to do to achieve this?
I'm using Umbraco 8.5.5
Thanks David
For Umbraco 8: Managed to do this by copying the Macro Handler, Macro Serializer, Macro Tracker and Macro Checker
Create following files: RelationTypeHandler
RelationTypeSerializer
RelationTypeTracker
RelationTypeChecker
Then resolve the dependencies in your composer:
Update your usync.config file
Compile your code, open umbraco and just click save on all existing Relation Types
Then import and export to include files in source control usync folder
Hi all. I'm wondering if there's any thoughts on including this RelationTypes handler in uSync, whether it's already on the roadmap - or part of another package (that I'm not aware of)?
It's the first time that I'm using RelationTypes and uSync on the same project, so interested to see how this pans out.
Many thanks,
- Lee
Hi Lee,
Its in the wings, ready for our next release:
https://github.com/KevinJump/uSync8/commit/28bcf4733f82ab65969dbc6adf2306807ca18908
In terms of timescales we are currently working out if we are waiting for Umbraco 8.7 - because we might want to also squish all the block editor stuff in too.
It would also be good to get feedback on how it might work.
at the moment we are syncing RelationTypes by default with options to also Sync the actual Relations.
also settings to not sync certain relation types (e.g you might not want some of the built in ones).
If you can think of any other use cases / configuration options that would be good.
Thanks Kevin! I did have a search around your uSync repos, but didn't spot the branch/commit (probably because GitHub search only works in the main/default branch).
Looking forward to v8.7!
I'll have a think about my use cases, see how I'd expect it work, etc - and feed back.
Thanks again!
- Lee
Yeah we've just been talking about it, we might be settling on the next release being uSync 8.6 and three quarters. 😊
is working on a reply...