uSync icon indicating copy to clipboard operation
uSync copied to clipboard

Contnet Type Allowed Templates that is not registed on uSync got removed

Open MahmoudAbdelsallam opened this issue 3 years ago • 2 comments

Describe the bug We have a deployed umbraco instance where customers can add custom templates and link them with Document Types. In that case this change is not committed to our repository. In our next deployment this link got removed as it is not registered in uSync.

To Reproduce Steps to reproduce the behavior: 1 - Go to document type 2- Link extra template 3- Then trigger another deployment from your repository image

Expected behavior uSync should not remove existing links between DocumentTypes and AllowedTemplates if they are not configured in the config file For Ex: Template1 & Template2 are configured in the confige file, that doesn't mean that those templates are the only ones. It means that uSync should make sure to link them to DocumentType and that's it. May be we can add a configuration if the extra linked templates should be removed.

<ContentType Key="b51331b0-cd02-404f-82b9-24489a238575" Alias="contentPage" Level="1">
  <Info>
    <Name>Content Page</Name>
    <Icon>icon-document</Icon>
    <Thumbnail>folder.png</Thumbnail>
    <Description></Description>
    <AllowAtRoot>False</AllowAtRoot>
    <IsListView>False</IsListView>
    <Variations>Nothing</Variations>
    <IsElement>false</IsElement>
    <HistoryCleanup>
      <PreventCleanup>False</PreventCleanup>
      <KeepAllVersionsNewerThanDays></KeepAllVersionsNewerThanDays>
      <KeepLatestVersionPerDayForDays></KeepLatestVersionPerDayForDays>
    </HistoryCleanup>
    <Compositions>
      <Composition Key="d2b99d9d-23cd-40e9-8417-f49d22c20e6d">navigationBase</Composition>
    </Compositions>
    <DefaultTemplate>amcsPublicPage</DefaultTemplate>
    <AllowedTemplates>
      **<Template Key="29ce7840-92fa-4c34-b45c-db5ff0416dfa">Template1</Template>
      <Template Key="2b37d72c-b8ea-4a02-b166-2944dce100e9">Template2</Template>**
    </AllowedTemplates>

About your Site (please complete the following information):

  • Umbraco Version:8.18.5
  • uSync Version: v8.11.1 + Content Edition (v8.11.1)
  • Browser chrome

cc/@KevinJump

MahmoudAbdelsallam avatar Oct 10 '22 11:10 MahmoudAbdelsallam

Hi,

Unfortunately, this is the expected behaviour - uSync will sync things exactly like they appear the aim is to have both sites be identical in every way.

However we are always option to adding options to change behaviour, we could for example have a 'KeepTemplates* option that would not remove them - but this would be an enhancement not a bug fix

KevinJump avatar Oct 20 '22 10:10 KevinJump

Thanks @KevinJump, For now I have created a custom ContentTypeHandler & Serializer and added AddClientCustomTemplatesTouSyncAllowedTemplates to serializer

 [SyncHandler("customContentTypeHandler", "DocTypes", "ContentTypes", uSyncBackOfficeConstants.Priorites.ContentTypes,
           IsTwoPass = true, Icon = "icon-item-arrangement", EntityType = UdiEntityType.DocumentType)]
  public class CustomContentTypeHandler : ContentTypeHandler
  {
    private readonly IContentTypeService contentTypeService;

    public CustomContentTypeHandler(
        IContentTypeService contentTypeService,
        IEntityService entityService,
        IProfilingLogger logger,
        AppCaches appCaches,
        CustomContentTypeSerlializer serializer,
        ISyncItemFactory syncItemFactory,
        SyncFileService syncFileService)
        : base(contentTypeService, entityService, logger, appCaches, serializer, syncItemFactory, syncFileService)
    {
      this.contentTypeService = contentTypeService;
    }
  }
[SyncSerializer("EBCD359D-295C-4B8B-B5CF-D44690DC5FB1", "CustomContentTypeSerlializer", uSyncConstants.Serialization.ContentType)]
 public class CustomContentTypeSerlializer : ContentTypeSerializer
 {
   private readonly IContentTypeService contentTypeService;
   private readonly IFileService fileService;

   public CustomContentTypeSerlializer(
       IEntityService entityService, ILogger logger,
       IDataTypeService dataTypeService,
       IContentTypeService contentTypeService,
       IFileService fileService)
       : base(entityService, logger, dataTypeService, contentTypeService, fileService)
   {
     this.contentTypeService = contentTypeService;
     this.fileService = fileService;
   }


   protected override SyncAttempt<IContentType> DeserializeCore(XElement node, SyncSerializerOptions options)
   {
     var attempt = FindOrCreate(node);
     if (!attempt.Success) throw attempt.Exception;

     var item = attempt.Result;

     var details = new List<uSyncChange>();

     details.AddRange(DeserializeBase(item, node));
     details.AddRange(DeserializeTabs(item, node));
     details.AddRange(DeserializeProperties(item, node, options));

     // content type only property stuff.
     details.AddRange(DeserializeContentTypeProperties(item, node));

     // templates 
     details.AddRange(DeserializeTemplates(item, node));

     return SyncAttempt<IContentType>.Succeed(item.Name, item, ChangeType.Import, details);
   }

   protected override IEnumerable<uSyncChange> DeserializeExtraProperties(IContentType item, PropertyType property, XElement node)
   {
     var variations = node.Element("Variations").ValueOrDefault(ContentVariation.Nothing);
     if (property.Variations != variations)
     {
       var change = uSyncChange.Update("Property/Variations", "Variations", property.Variations, variations);

       property.Variations = variations;

       return change.AsEnumerableOfOne();
     }

     return Enumerable.Empty<uSyncChange>();
   }

   private IEnumerable<uSyncChange> DeserializeContentTypeProperties(IContentType item, XElement node)
   {
     var info = node?.Element("Info");
     if (info == null) return Enumerable.Empty<uSyncChange>();

     var changes = new List<uSyncChange>();

     var isContainer = info.Element("IsListView").ValueOrDefault(false);
     if (item.IsContainer != isContainer)
     {
       changes.AddUpdate("IsListView", item.IsContainer, isContainer, "Info/IsListView");
       item.IsContainer = isContainer;
     }

     var masterTemplate = info.Element("DefaultTemplate").ValueOrDefault(string.Empty);
     if (!string.IsNullOrEmpty(masterTemplate))
     {
       var template = fileService.GetTemplate(masterTemplate);
       if (template != null)
       {
         if (item.DefaultTemplate == null || template.Alias != item.DefaultTemplate.Alias)
         {
           changes.AddUpdate("DefaultTemplate", item.DefaultTemplate?.Alias ?? string.Empty, masterTemplate, "DefaultTemplate");
           item.SetDefaultTemplate(template);
         }
       }
       else
       {
         // elements don't have a defaultTemplate, but it can be valid to have the old defaultTemplate in the db.
         // (it would then re-appear if the user untoggles is element) See issue #203
         //
         // So we only log this as a problem if the default template is missing on a non-element doctype. 
         if (!item.IsElement)
         {

           changes.AddUpdate("DefaultTemplate", item.DefaultTemplate?.Alias ?? string.Empty, "Cannot find Template", "DefaultTemplate", false);
         }
       }
     }

     return changes;
   }

   private IEnumerable<uSyncChange> DeserializeTemplates(IContentType contentType, XElement node)
   {
     var templates = node?.Element("Info")?.Element("AllowedTemplates");
     if (templates == null) return Enumerable.Empty<uSyncChange>();

     var uSyncAllowedTemplates = new List<ITemplate>();
     var changes = new List<uSyncChange>();


     foreach (var template in templates.Elements("Template"))
     {
       var alias = template.Value;
       var key = template.Attribute("Key").ValueOrDefault(Guid.Empty);

       var templateItem = GetTemplate(alias, key);

       if (templateItem != null)
       {
         logger.Debug<ContentTypeSerializer>("Adding Template: {0}", templateItem.Alias);
         uSyncAllowedTemplates.Add(templateItem);
       }
     }
     AddClientCustomTemplatesTouSyncAllowedTemplates(contentType, uSyncAllowedTemplates);

     var currentTemplates = string.Join(",", contentType.AllowedTemplates.Select(x => x.Alias).OrderBy(x => x));
     var newTemplates = string.Join(",", uSyncAllowedTemplates.Select(x => x.Alias).OrderBy(x => x));

     if (currentTemplates != newTemplates)
     {
       changes.AddUpdate("AllowedTemplates", currentTemplates, newTemplates, "AllowedTemplates");
     }

     contentType.AllowedTemplates = uSyncAllowedTemplates;

     return changes;
   }

   private void **AddClientCustomTemplatesTouSyncAllowedTemplates**(IContentType contentType, List<ITemplate> uSyncAllowedTemplates)
   {
     var currentTemplateAliases = contentType.AllowedTemplates.Select(x => x.Alias).OrderBy(x => x);
     var newTemplateAliases = uSyncAllowedTemplates.Select(x => x.Alias).OrderBy(x => x);
     var customTemplatesAliasesAddedByClient = currentTemplateAliases.Except(newTemplateAliases);
     foreach (var templateAlias in customTemplatesAliasesAddedByClient)
     {
       var templateItem = GetTemplate(templateAlias, Guid.Empty);
       if (templateItem != null)
       {
         logger.Debug<ContentTypeSerializer>("Adding Template: {0}", templateItem.Alias);
         uSyncAllowedTemplates.Add(templateItem);
       }
     }
   }

   private ITemplate GetTemplate(string alias, Guid key)
   {
     var templateItem = default(ITemplate);
     if (key != Guid.Empty)
       templateItem = fileService.GetTemplate(key);

     if (templateItem == null)
       templateItem = fileService.GetTemplate(alias);

     return templateItem;
   }
 }

MahmoudAbdelsallam avatar Oct 20 '22 16:10 MahmoudAbdelsallam

Added "KeepTemplates" option for next v11 version

KevinJump avatar Feb 06 '23 15:02 KevinJump