Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for parent and child page restrictions in page_layout definition #1776

Conversation

mickenorlen
Copy link
Contributor

@mickenorlen mickenorlen commented Apr 5, 2020

Update 8/7

See comment: #1776 (comment)


What is this pull request for?

Add support for restricting parent/child_pages:

Update 17/5

Renamed sub_pages to child_pages and added include/exclude as suggested by @mamhoff.

# config/alchemy/page_layouts.yml

- name: blog
  child_pages: 
     include: [blog_post] # array of allowed child_pages.

- name: blog_post
  parent_pages: 
     include: [blog] # array of parent pages under which page can be created
  child_pages: false # hides add child_page button from page tree

- name: no_blogs_pages # Excludes example
  child_pages:
    exclude: [blog] 
  parent_pages:
    exclude: [blog]

Note

  • If child_pages = false it will entirely hide the create child_page button (+) in page_tree. See screenshot 1.
  • if selectable_layouts.length == 1 i added autoselect and removed blank option in create selection. See screenshot 2

Changes in the code

  • Passed in the page_layout of the parent_page to PageLayout.selectable_layouts to be able to check the definitions.
  • Added allowing_child_pages: page.definition['child_pages'] != false to the page_tree_serializer so i could hide the create (+) button.

Screenshots

  1. No (+) button if child_pages = false
    image

  2. No blank/"Please choose" option when only 1 to choose.
    image

Tests

I wanted to check with you if the overall implementation looks good before fixing the tests. Currently 2 fails saying undefined method page_layout for nil:NilClass

rspec ./spec/features/admin/page_creation_feature_spec.rb:21 # Page creation overlay GUI when having a Page in the clipboard contains tabs for creating a new page and pasting from clipboard
rspec ./spec/requests/alchemy/admin/pages_controller_spec.rb:261 # Alchemy::Admin::PagesController with logged in editor user #new with current language present pages in clipboard should load all pages from clipboard

Checklist

  • I have followed Pull Request guidelines
  • I have added a detailed description into each commit message
  • I have added tests to cover this change

@mickenorlen mickenorlen force-pushed the parent-and-sub-page-restrictions branch 3 times, most recently from af1fc18 to 8209294 Compare April 5, 2020 14:27
Copy link
Contributor

@mamhoff mamhoff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a few implementation comments, and I would like to suggest to only implement ONE of sub_pages or parent_pages. If a page layout can only have certain subpages, then that's all you should ever need, am I right?

app/serializers/alchemy/page_tree_serializer.rb Outdated Show resolved Hide resolved
required: true,
selected: @page_layouts.length == 1 ? @page_layouts.first : nil,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

page_layouts.first.presence instead would do the trick

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice catch will fix next round

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I don't think i can change this as i select first only if length == 1 - Not if first is present

lib/alchemy/page_layout.rb Outdated Show resolved Hide resolved
#
def selectable_layouts(language_id, only_layoutpages = false)
def selectable_layouts(language_id, only_layoutpages = false, page_layout_name = nil)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method selectable_layouts has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.

@mickenorlen mickenorlen force-pushed the parent-and-sub-page-restrictions branch from 1cf16b7 to 2494f31 Compare May 17, 2020 12:45
@mickenorlen mickenorlen changed the title Add support for parent and sub page restrictions in page_layout definition Add support for parent and child page restrictions in page_layout definition May 17, 2020
@mickenorlen mickenorlen force-pushed the parent-and-sub-page-restrictions branch 2 times, most recently from 03bdcef to f6bd940 Compare May 17, 2020 13:04
Copy link
Member

@tvdeyen tvdeyen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

I like the overall idea of this feature a lot. But the implementation should be much easier. I do not see a need for child pages being able to define parent layouts. This does not make any sense. Children can not exist without a parent.

So in my thoughts something like

- name: blog
  sub_pages:
    only: post

and

- name: standard
  sub_pages:
    except:
      - post
      - blog

is a great API we would except.

The selectable layouts methods are worth refactoring so that its easier to understand what is going on. In the end it should be easy as:

All defined page layouts for that site - already taken unique ones - not allowed layouts = selectable layouts for the new page

And we need tests of course.

Again, thanks for that feature. I like it.

app/models/alchemy/page.rb Outdated Show resolved Hide resolved
app/models/alchemy/page.rb Outdated Show resolved Hide resolved
app/serializers/alchemy/page_tree_serializer.rb Outdated Show resolved Hide resolved
app/views/alchemy/admin/pages/_page.html.erb Outdated Show resolved Hide resolved
lib/alchemy/page_layout.rb Outdated Show resolved Hide resolved
lib/alchemy/page_layout.rb Outdated Show resolved Hide resolved
(excludes && !excludes.include?(layout['name']))
end

def layout_available_from_child?(layout)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why we would need that. We are never able to create a parent for an existing child. That's not possible. Can you elaborate what this is for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not about creating a parent for a child. But under which parents the layout will be available. Previously there was a setting hide = true which made certain layouts hidden from creation. I just deleted this hide setting from the conditions in my last push as this can be achived with `layout['parent_pages'] = false.

But the reason why I think this feature makes sense to have is the following. Imagine that you have a mature website with some 15 page layouts available. Then you want a new type of blog post layout special_blog_post that should only be creatable under the blog layout.

With the new feature you simply set parent_pages['only'] = ['blog'] in the special_blog_post settings.

Without it you would have to revisit all other page_layout and append `child_pages['except'] = [ ... 'special_blog_post']

... Which feels a lot more tedious/error prone. In the end this logic is only executed on admin page creation, and we're already looping through all layouts to check the rest of the conditions - so i dont think it could have any significant impact on performance?

What do you think?

#
def selectable_layouts(language_id, only_layoutpages = false)
def selectable_layouts(language_id, only_layoutpages = false, page_layout_name = nil)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree this method is overly complex and combined with the other methods its worth refactoring. I think this can be implemented much easier.

Copy link
Contributor Author

@mickenorlen mickenorlen Jun 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I can simplify this. I added more comments to the code to clear up any misunderstanding.

Also - I chose to call it parent_layout_name instead of parent_page_layout to make it clear that it is a name (String) and not a layout (Object/hash) from definition we're expecting - As has been done in a bunch of other places there

@language_id = language_id
@page_layout = page_layout_name.present? ? all.detect{ |layout| layout['name'] == page_layout_name } : nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary in here? We not filter the available page layouts with select reject instead?

Copy link
Contributor Author

@mickenorlen mickenorlen Jun 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gets the layout of the parent page (Object/hash from definition where we can check for child_page restrictions) from the parent layout name (String).

Then just below we filter with select and all the conditions.

@mickenorlen
Copy link
Contributor Author

mickenorlen commented Jun 29, 2020

@tvdeyen Thanks for your comments. I updated most of the things you suggested and added tests.

Some questions/thoughts:

  • See code comments where i explain/argue for the parent_pages setting
  • I see you prefer the term sub_pages. But IF you agree the parent_pages setting is something to keep in the end - I think parent_pages and child_pages are more naturally related terms. What do you think?

The selectable layouts methods are worth refactoring so that its easier to understand what is going on. In the end it should be easy as:

All defined page layouts for that site - already taken unique ones - not allowed layouts = selectable layouts for the new page

This seems kind of like what we have?

In method selectable_layouts - This code is from before and I'm guessing it is still relevant? Basically we select filter through all the available layouts with a condition to check if its a layoutpage or not...

  all.select do |layout|
    if only_layoutpages
      layout['layoutpage'] && layout_available?(layout)
    else
      !layout['layoutpage'] && layout_available?(layout)
    end
  end

Then below we have the conditions tested for each layout in the select filter. I only added the first two (and removed the old redundant hide see code comments about parent_pages setting).

  def layout_available?(layout)
    layout_available_from_parent?(layout) && layout_available_from_child?(layout) &&
      !already_taken?(layout) && available_on_site?(layout)
  end

Which equates to: (?)

All defined page layouts for that site - already taken unique ones - not allowed layouts = selectable layouts for the new page

So I'm not sure how you want to simplify this.

@mickenorlen mickenorlen requested a review from tvdeyen June 29, 2020 13:19
#
def selectable_layouts(language_id, only_layoutpages = false)
def selectable_layouts(language_id, only_layoutpages = false, parent_layout_name = nil)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method selectable_layouts has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.

@mickenorlen mickenorlen force-pushed the parent-and-sub-page-restrictions branch from 018ff48 to 8e7d2d7 Compare July 8, 2020 08:16
@tvdeyen
Copy link
Member

tvdeyen commented Aug 26, 2020

Could you please extract the "No please choose if only one layout available" feature into a dedicated PR?

Copy link
Member

@tvdeyen tvdeyen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at this again today and although I think this is a nice way to structure pages I still think that the page layout code is getting more and more complex. Since I need to maintain this going forward I need more time to get convinced. Maybe refactoring the code into smaller understandable classes and methods would help. But in the current form I tend to not accept this although I like that feature.

@@ -57,9 +57,9 @@ def info

def new
@page ||= Page.new(layoutpage: params[:layoutpage] == "true", parent_id: params[:parent_id])
@page_layouts = PageLayout.layouts_for_select(@current_language.id, @page.layoutpage?)
@page_layouts = PageLayout.layouts_for_select(@current_language.id, @page.layoutpage?, @page.parent &.page_layout)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be?

Suggested change
@page_layouts = PageLayout.layouts_for_select(@current_language.id, @page.layoutpage?, @page.parent &.page_layout)
@page_layouts = PageLayout.layouts_for_select(@current_language.id, @page.layoutpage?, @page.parent&.page_layout)

A test would have could this. Could you please add one?

app/controllers/alchemy/admin/pages_controller.rb Outdated Show resolved Hide resolved
@@ -55,6 +55,7 @@ def page_hash(page, has_children, level, folded)
public: page.public?,
restricted: page.restricted?,
page_layout: page.page_layout,
child_pages_allowed: page.definition["child_pages"] != false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not like the name of this attribute

allowed_page_types, or maybe nestable_pages nestable_child_pages?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now i named it for what it actually is show_create_page_btn. Which can be true even if there are no pages available, as the button is only hidden if definition['child_pages'] == false.

Also added tests for controller method calls. I've been using this feature for quite some time already, [space] &. works just as fine as [no_space].& - but i still removed the space as you like.

I think the implementation is good now and for me the feature is essential for controlling the page tree structure. It is also just as you requested it to be as explained in this comment #1776 (comment)

I would say it cannot be simplified more. You have to give me more concrete feedback if you still disagree.

@mickenorlen mickenorlen requested a review from tvdeyen October 14, 2020 09:26
@tvdeyen tvdeyen changed the base branch from master to main October 26, 2020 19:48
@tvdeyen
Copy link
Member

tvdeyen commented Oct 26, 2020

@mickenorlen we switched the default branch to main. I already updated this PR, but you want to pull the main branch and rebase this branch against this as well, so you get future updates. sorry for any inconvenience and thanks for the contribution.

…inition

child_pages lists which pages can be created under given page layout. parent_pages lists under which page layouts given page can be created. Both will determine which page layouts are available for selection. Set by: child/parent_pages: {include/exclude: [layout_name, ...]
@mickenorlen mickenorlen force-pushed the parent-and-sub-page-restrictions branch from 5aea006 to e1fe6ff Compare October 29, 2020 13:11
@mickenorlen
Copy link
Contributor Author

@tvdeyen Force pushed a fresh single squash merge commit into main as rebase was a mess.

@github-actions
Copy link

This pull request has not seen any activiy in a long time.
Probably because of missing tests or a necessary rebase.
This PR will be closed in 7 days if no further activity happens.

@github-actions github-actions bot added the Stale label Mar 21, 2022
@github-actions
Copy link

This pull request has not seen any activiy in a long time.
Probably because of missing tests or a necessary rebase.
Please open a new PR to latest main if you want to continue working on this.
Thanks for the contribution.

@github-actions github-actions bot closed this Mar 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants