umbraco / Umbraco-CMS

Umbraco is a free and open source .NET content management system helping you deliver delightful digital experiences.
https://umbraco.com
MIT License
4.49k stars 2.69k forks source link

Parent Node of Children is null, when Node is published but Parent is unpublished #15328

Open cf-marc opened 11 months ago

cf-marc commented 11 months ago

Which Umbraco version are you using? (Please write the exact version, example: 10.1.0)

12.3.1

Bug summary

Dear Umbraco Team,

We hope this message finds you well.

We wanted to bring to your attention an issue we've encountered while implementing a custom navigation API for our Headless solution. To ensure that unpublished items are also included when users are previewing the Site, we've been loading the content item of the StartNode using umbracoContext.Content.GetById(isPreview, currentNodeGuid).

However, we've come across a specific edge case that we'd like assistance with. Here's the scenario:

We created a new node and added some child nodes. The parent node was unpublished, but the children were not.

You can find a visual representation of the scenario in the following image: grafik

We are going to loop over all content items to create the navigation.

    public IActionResult Get(Guid currentNodeGuid, [FromQuery] string culture, [FromQuery] bool isPreview, int level = -1)
    {
        ...
        _variationContextAccessor.VariationContext = new VariationContext(culture);
        IPublishedContent currentNode = umbracoContext.Content.GetById(isPreview, currentNodeGuid);
        if(currentNode == null)
        {
            return NotFound();
        }

        IPublishedContent startNode = currentNode;
        if(level > 0)
        {
            // Skipping the root node (assuming that home is the direct child of root)
            startNode = currentNode.AncestorOrSelf(level + 1);
        }

        List<NavigationItem> navigationItems = GetNavigation(startNode, culture, isPreview);
        ...
    }

    private List<NavigationItem> GetNavigation(IPublishedContent startItem, string culture, bool isPreview)
    {
        List<NavigationItem> mainNavigationItems = new List<NavigationItem>();
        foreach(IPublishedContent navigationItem in startItem.Children)
        {
            NavigationItem navItem = GetNavigationItem(navigationItem, culture, isPreview);
            if(navItem != null)
            {
                mainNavigationItems.Add(navItem);
            }
        }

        return mainNavigationItems;
    }

    private NavigationItem GetNavigationItem(IPublishedContent navigationItem, string culture, bool isPreview = false)
    {
        if(navigationItem is ISettings settings)
        {
            return new NavigationItem()
            {
                ...
                ParentKey = (navigationItem.Level == 2 ? null : navigationItem.Parent.Key),
                Children = GetChildren(navigationItem, isPreview)
                                ...
            };
        }

        return null;
    }

    private List<NavigationItem> GetChildren(IPublishedContent content, bool isPreview = false)
    {
        if(content?.IsVisible() ?? false)
        {
            List<NavigationItem> navigationItems = new List<NavigationItem>();
            foreach(IPublishedContent childContent in content.Children(_variationContextAccessor).Where(m => m.IsVisible()))
            {
                NavigationItem navItem = GetNavigationItem(childContent, _variationContextAccessor.VariationContext.Culture, isPreview);
                if(navItem != null)
                {
                    navigationItems.Add(navItem);
                }
            }
            return navigationItems;
        }

        return null;
    }
}

Currently, as we loop over all content items to generate the navigation, the "Blog" item from the screen is included. This is because we load the startItem with preview = true. When we access the "Test Blog Article," the parent property of IPublishedContent is null.

We appreciate your support in addressing this issue.

Specifics

No response

Steps to reproduce

Expected result / actual result

The parent node of IPublishedContent should not be null, if the node is loaded with the preview flag.

github-actions[bot] commented 11 months ago

Hi there @cf-marc!

Firstly, a big thank you for raising this issue. Every piece of feedback we receive helps us to make Umbraco better.

We really appreciate your patience while we wait for our team to have a look at this but we wanted to let you know that we see this and share with you the plan for what comes next.

We wish we could work with everyone directly and assess your issue immediately but we're in the fortunate position of having lots of contributions to work with and only a few humans who are able to do it. We are making progress though and in the meantime, we will keep you in the loop and let you know when we have any questions.

Thanks, from your friendly Umbraco GitHub bot :robot: :slightly_smiling_face:

kjac commented 11 months ago

Hi @cf-marc,

Thanks for reaching out. I can reproduce this in V13 as well.

I understand what you're trying to do here, but if you're not careful, your approach may result in a performance degradation as the content tree grows in size. A recursive traversion of published content is generally not recommended - even if it is all in memory, it may eventually become sluggish as the dataset grows. Not to mention CPU intensive.

Please consider using an editor defined (fixed) navigation instead, or a search based approach. You might even be able to utilize the Delivery API query service (IApiContentQueryService) to procure your navigation result set. It is yet to be documented, but here is an example of it being used for querying. A blog post might just be in the works 😉

If you still choose to pursue your current solution, I would pass the parent content down the call chain, as it seems to be available in the Children enumeration. Then you don't need to rely on the Parent property.

As for this issue, we'd love some help getting it fixed. I'm putting this up for grabs.

A fix for this entails:

In preview mode

  1. Unpublished items should be part of the Children collection of any parent. This seems to work as of today.
  2. Unpublished parents should be linked in the Parent property of any child. This does not seem to work as of today.

In non-preview mode

Everything should work as-is, meaning unpublished items should never appear in Children or as Parent.

Tests, tests, tests

A fix must include tests (unit or integration) that proves both the current functionality for non-preview mode and the working functionality for preview mode.

github-actions[bot] commented 11 months ago

Hi @cf-marc,

We're writing to let you know that we would love some help with this issue. We feel that this issue is ideal to flag for a community member to work on it. Once flagged here, folk looking for issues to work on will know to look at yours. Of course, please feel free work on this yourself ;-). If there are any changes to this status, we'll be sure to let you know.

For more information about issues and states, have a look at this blog post.

Thanks muchly, from your friendly Umbraco GitHub bot :-)