Which Umbraco version are you using? (Please write the exact version, example: 10.1.0)
10.8.2
Bug summary
When using custom variations using segments, it is noted that changes to the name of a specific variation is not saved and actually reverts back upon saving or saving and publishing. Changing the name of the bottom-most variation in the list seems to change the name for all segment variations.
Specifics
I followed the tutorial as closely as I could from here.
I had to make some changes to actually make it work, so I ended up with the following code to generate my segments:
internal class ContentSegmentRenderHandler : INotificationHandler<SendingContentNotification>
{
private readonly ISegmentProvider _segmentProvider;
private readonly IContentTypeService _contentTypeService;
public ContentSegmentRenderHandler(ISegmentProvider segmentProvider, IContentTypeService contentTypeService)
{
_segmentProvider = segmentProvider;
_contentTypeService = contentTypeService;
}
public void Handle(SendingContentNotification notification)
{
/* There are several rules for segmentation
* - A page has to be eligible for segmentation, which is decided by the website itself.
* - Any "original" version has no segment value
* - For each original version, we should have additional versions for each segment
* - A segment should be added if no version already exists on the content model
*/
if (!_segmentProvider.IsEligible(notification.Content)) return;
var segments = _segmentProvider.GetSegments();
var contentType = _contentTypeService.Get(notification.Content.ContentTypeId.Value);
if (contentType is null) return;
var props = contentType.PropertyTypes.Where(p => p.VariesBySegment()).Select(p => p.Alias).ToHashSet();
props.UnionWith(contentType.CompositionPropertyGroups.SelectMany(p => p.PropertyTypes).Where(p => p.VariesBySegment()).Select(p => p.Alias));
if (!props.Any()) return;
// The frontend depends on the order of the segments,
// therefore it is important to ensure a consistent order of all different variations.
var newVariants = new List<ContentVariantDisplay>(notification.Content.Variants);
newVariants = EnsureProperDisplayNames(newVariants, segments);
newVariants = EnsureSegmentForEachVariant(newVariants, segments, props);
notification.Content.Variants = newVariants
.OrderBy(v => IsDefaultLanguage(v) ? 0 : 1)
.ThenBy(v => IsDefaultSegment(v) ? 0 : 1)
.ThenBy(v => v?.Language?.Name)
.ThenBy(v => v.Segment)
.ToList();
}
private static bool IsDefaultLanguage(ContentVariantDisplay variant) =>
variant.Language == null || variant.Language.IsDefault;
private static bool IsDefaultSegment(ContentVariantDisplay variant) => variant.Segment == null;
private List<ContentVariantDisplay> EnsureSegmentForEachVariant(List<ContentVariantDisplay> newVariants, IEnumerable<Segment> segments, ISet<string> variantProps)
{
var additionalVariants = new List<ContentVariantDisplay>();
// A variant is original if the value for segment is empty
foreach (var variant in newVariants.Where(v => string.IsNullOrWhiteSpace(v.Segment)))
{
// A variant is the same if their language and segment are equal.
foreach (var segment in segments.Where(s => !newVariants.Any(v => v.Language?.Id == variant.Language?.Id && v.Segment == s.Id)))
{
var newVariant = new ContentVariantDisplay
{
AllowedActions = new List<string>(variant.AllowedActions),
DisplayName = segment.Name,
Language = variant.Language,
Segment = segment.Id,
State = ContentSavedState.NotCreated,
Tabs = variant.Tabs.Select(x => new Tab<ContentPropertyDisplay>
{
Alias = x.Alias,
IsActive = x.IsActive,
Expanded = x.Expanded,
Id = x.Id,
Key = x.Key,
Label = x.Label,
Type = x.Type,
Properties = x.Properties?.Select(p => new ContentPropertyDisplay
{
Alias = p.Alias,
Config = p.Config,
ConfigNullable = p.ConfigNullable,
Culture = p.Culture,
DataTypeKey = p.DataTypeKey,
Description = p.Description,
Editor = p.Editor,
HideLabel = p.HideLabel,
Id = p.Id,
IsSensitive = p.IsSensitive,
Label = p.Label,
LabelOnTop = p.LabelOnTop,
PropertyEditor = p.PropertyEditor,
Readonly = p.Readonly,
Segment = variantProps.Contains(p.Alias) ? segment.Id : null,
SupportsReadOnly = p.SupportsReadOnly,
Validation = p.Validation,
Value = variantProps.Contains(p.Alias) ? null : p.Value,
Variations = p.Variations,
View = p.View,
}),
})
};
additionalVariants.Add(newVariant);
}
}
newVariants.AddRange(additionalVariants);
return newVariants;
}
private List<ContentVariantDisplay> EnsureProperDisplayNames(List<ContentVariantDisplay> newVariants, IEnumerable<Segment> segments)
{
// Always assign the current name of the segment to the displayname to ensure that the frontend always shows an up-to-date name
foreach(var variant in newVariants)
{
if (string.IsNullOrWhiteSpace(variant.Segment)) continue;
var segment = segments.FirstOrDefault(s => s.Id == variant.Segment);
if (segment is null) continue;
variant.DisplayName = segment.Name;
}
return newVariants;
}
}
Notice the ISegmentProvider, which is a simple service that returns a list of models like this:
public record Segment(string Name, string Id, bool Default = false);
It also decides which document types are elligible for segmentation. The following implementation of ISegmentProvider should suffice to reproduce the issue:
public interface ISegmentProvider
{
IEnumerable<Segment> GetSegments();
bool IsEligible(ContentItemDisplay contentItem);
}
public class TestSegmentProvider
: ISegmentProvider
{
public bool IsEligible(ContentItemDisplay contentItem) => true;
public IEnumerable<Segment> GetSegments()
=> new []
{
new Segment("First Segment", "1111", true),
new Segment("Second Segment", "2222", false),
new Segment("Third Segment", "3333", false)
}
}
A quick scan through the Umbraco source, revealed the following line of code:
contentSave.PersistedContent.SetCultureName(variant.Name, variant.Culture);
To be found here. This line indicates that segments are not considered while naming a content variation. Something worth looking into.
I have attempted to work around this issue by copying the name of the default variation to the new segments in the ContentSegmentRenderHandler, but doing so results in spotty behaviour in the frontend with segments.
Steps to reproduce
- Create a document type and enable variation by segment.
You need to make the checkbox visible for that first. I wrote some code for that in my blog. You can find a VariantsServerVariablesParser there. That code snippet should allow you to enable variation by segment on a document type
- Implement segment generation. In the 'specifics' chapter of this issue, I provide code snippets to generate some segments.
- Make an instance of the document type with variation by segment enabled, optionally enter some content and save and publish
- Reload the page by pressing F5.
- Check the variations in the top-right corner in the name input and create a new variation by segment. Also enter a name (preferably a different name than the default) and save and publish.
- Notice that all variations always have the same name and notice that changing the name on some variations doesn't work and on other variations changes the name for all.
Expected result / actual result
I expect that each variation can have it's own name or that variations by segment have their name field replicated from the "default" and greyed out so I can't overwrite it. I expect the "default" variation to be leading when naming a document type.
This item has been added to our backlog AB#37563
Which Umbraco version are you using? (Please write the exact version, example: 10.1.0)
10.8.2
Bug summary
When using custom variations using segments, it is noted that changes to the name of a specific variation is not saved and actually reverts back upon saving or saving and publishing. Changing the name of the bottom-most variation in the list seems to change the name for all segment variations.
Specifics
I followed the tutorial as closely as I could from here.
I had to make some changes to actually make it work, so I ended up with the following code to generate my segments:
Notice the
ISegmentProvider, which is a simple service that returns a list of models like this:It also decides which document types are elligible for segmentation. The following implementation of
ISegmentProvidershould suffice to reproduce the issue:A quick scan through the Umbraco source, revealed the following line of code:
To be found here. This line indicates that segments are not considered while naming a content variation. Something worth looking into.
I have attempted to work around this issue by copying the name of the default variation to the new segments in the
ContentSegmentRenderHandler, but doing so results in spotty behaviour in the frontend with segments.Steps to reproduce
You need to make the checkbox visible for that first. I wrote some code for that in my blog. You can find a
VariantsServerVariablesParserthere. That code snippet should allow you to enable variation by segment on a document typeExpected result / actual result
I expect that each variation can have it's own name or that variations by segment have their name field replicated from the "default" and greyed out so I can't overwrite it. I expect the "default" variation to be leading when naming a document type.
This item has been added to our backlog AB#37563