avo icon indicating copy to clipboard operation
avo copied to clipboard

Strange tab behaviour in avo 3.x

Open iainbeeston opened this issue 2 years ago • 5 comments

Describe the bug

In avo 3.x tabs behave differently to avo 2.x. I've found two related bugs:

  1. If you define tabs inside the fields method of a resource, the breadcrumbs and actions are not shown on the show and edit views.
  2. If you define the tabs inside a main_panel block, the actions and breadcrumbs will appear (on show and view) but has_many fields raise an error (see below).

On avo 2.x main_panel wasn't necessary to make actions and breadcrumbs appear. I've only mentioned it because I found that (as a hack) that makes tabs usable... except when you have a has_many field.

Steps to Reproduce

Steps to reproduce the behavior:

  1. Inside the fields method, put field definitions inside a tabs block, like this tabs { tab "foo" { field :bar, as: :text } }
  2. Load up avo and go to the show view for a resource
  3. The actions and breadcrumbs no longer appear.

Expected behavior & Actual behavior

The actions and breadcrumbs should appear the same as they do for the same resource without tabs.

Models and resource files

I've made a sample app that reproduces the issue as described, both with and without a main_panel block.

https://github.com/iainbeeston/avo-tabs-test

System configuration

Avo version: 3.3.3

Rails version: 7.0.8

Ruby version: 3.2.1

License type:

  • [x] Community
  • [ ] Pro
  • [ ] Advanced

Are you using Avo monkey patches, overriding views or view components?

  • [ ] Yes. If so, please post code samples.
  • [x] No

Screenshots or screen recordings

This is what it looks like if I use tabs without a main panel block.

Screenshot 2024-01-31 at 16 22 51 Screenshot 2024-01-31 at 16 23 13

This is what it looks like if I use tabs inside a main panel block.

Screenshot 2024-01-31 at 16 22 55 Screenshot 2024-01-31 at 16 23 09

Additional context

This is the backtrace of the error (when using tabs inside a main panel):

NoMethodError (undefined method `use_resource' for nil:NilClass):

avo (3.3.3) app/controllers/avo/associations_controller.rb:103:in `set_attachment_resource'
activesupport (7.0.8) lib/active_support/callbacks.rb:400:in `block in make_lambda'
activesupport (7.0.8) lib/active_support/callbacks.rb:180:in `block (2 levels) in halting_and_conditional'
actionpack (7.0.8) lib/abstract_controller/callbacks.rb:34:in `block (2 levels) in <module:Callbacks>'
activesupport (7.0.8) lib/active_support/callbacks.rb:181:in `block in halting_and_conditional'
activesupport (7.0.8) lib/active_support/callbacks.rb:595:in `block in invoke_before'
activesupport (7.0.8) lib/active_support/callbacks.rb:595:in `each'
activesupport (7.0.8) lib/active_support/callbacks.rb:595:in `invoke_before'
activesupport (7.0.8) lib/active_support/callbacks.rb:116:in `block in run_callbacks'
i18n (1.14.1) lib/i18n.rb:322:in `with_locale'
avo (3.3.3) app/controllers/avo/application_controller.rb:263:in `set_avo_locale'
activesupport (7.0.8) lib/active_support/callbacks.rb:127:in `block in run_callbacks'
actiontext (7.0.8) lib/action_text/rendering.rb:20:in `with_renderer'
actiontext (7.0.8) lib/action_text/engine.rb:69:in `block (4 levels) in <class:Engine>'
activesupport (7.0.8) lib/active_support/callbacks.rb:127:in `instance_exec'
activesupport (7.0.8) lib/active_support/callbacks.rb:127:in `block in run_callbacks'
activesupport (7.0.8) lib/active_support/callbacks.rb:138:in `run_callbacks'
actionpack (7.0.8) lib/abstract_controller/callbacks.rb:233:in `process_action'
actionpack (7.0.8) lib/action_controller/metal/rescue.rb:23:in `process_action'
actionpack (7.0.8) lib/action_controller/metal/instrumentation.rb:67:in `block in process_action'
activesupport (7.0.8) lib/active_support/notifications.rb:206:in `block in instrument'
activesupport (7.0.8) lib/active_support/notifications/instrumenter.rb:24:in `instrument'
activesupport (7.0.8) lib/active_support/notifications.rb:206:in `instrument'
actionpack (7.0.8) lib/action_controller/metal/instrumentation.rb:66:in `process_action'
actionpack (7.0.8) lib/action_controller/metal/params_wrapper.rb:259:in `process_action'
activerecord (7.0.8) lib/active_record/railties/controller_runtime.rb:27:in `process_action'
actionpack (7.0.8) lib/abstract_controller/base.rb:151:in `process'
actionview (7.0.8) lib/action_view/rendering.rb:39:in `process'
actionpack (7.0.8) lib/action_controller/metal.rb:188:in `dispatch'
actionpack (7.0.8) lib/action_controller/metal.rb:251:in `dispatch'
actionpack (7.0.8) lib/action_dispatch/routing/route_set.rb:49:in `dispatch'
actionpack (7.0.8) lib/action_dispatch/routing/route_set.rb:32:in `serve'
actionpack (7.0.8) lib/action_dispatch/journey/router.rb:50:in `block in serve'
actionpack (7.0.8) lib/action_dispatch/journey/router.rb:32:in `each'
actionpack (7.0.8) lib/action_dispatch/journey/router.rb:32:in `serve'
actionpack (7.0.8) lib/action_dispatch/routing/route_set.rb:852:in `call'
railties (7.0.8) lib/rails/engine.rb:530:in `call'
railties (7.0.8) lib/rails/railtie.rb:226:in `public_send'
railties (7.0.8) lib/rails/railtie.rb:226:in `method_missing'
actionpack (7.0.8) lib/action_dispatch/routing/mapper.rb:19:in `block in <class:Constraints>'
actionpack (7.0.8) lib/action_dispatch/routing/mapper.rb:48:in `serve'
actionpack (7.0.8) lib/action_dispatch/journey/router.rb:50:in `block in serve'
actionpack (7.0.8) lib/action_dispatch/journey/router.rb:32:in `each'
actionpack (7.0.8) lib/action_dispatch/journey/router.rb:32:in `serve'
actionpack (7.0.8) lib/action_dispatch/routing/route_set.rb:852:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/static.rb:23:in `call'
rack (2.2.8) lib/rack/static.rb:161:in `call'
rack (2.2.8) lib/rack/tempfile_reaper.rb:15:in `call'
rack (2.2.8) lib/rack/etag.rb:27:in `call'
rack (2.2.8) lib/rack/conditional_get.rb:27:in `call'
rack (2.2.8) lib/rack/head.rb:12:in `call'
actionpack (7.0.8) lib/action_dispatch/http/permissions_policy.rb:38:in `call'
actionpack (7.0.8) lib/action_dispatch/http/content_security_policy.rb:36:in `call'
rack (2.2.8) lib/rack/session/abstract/id.rb:266:in `context'
rack (2.2.8) lib/rack/session/abstract/id.rb:260:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/cookies.rb:704:in `call'
activerecord (7.0.8) lib/active_record/migration.rb:638:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/callbacks.rb:27:in `block in call'
activesupport (7.0.8) lib/active_support/callbacks.rb:99:in `run_callbacks'
actionpack (7.0.8) lib/action_dispatch/middleware/callbacks.rb:26:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/executor.rb:14:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/actionable_exceptions.rb:17:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/debug_exceptions.rb:28:in `call'
web-console (4.2.1) lib/web_console/middleware.rb:132:in `call_app'
web-console (4.2.1) lib/web_console/middleware.rb:28:in `block in call'
web-console (4.2.1) lib/web_console/middleware.rb:17:in `catch'
web-console (4.2.1) lib/web_console/middleware.rb:17:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/show_exceptions.rb:29:in `call'
railties (7.0.8) lib/rails/rack/logger.rb:40:in `call_app'
railties (7.0.8) lib/rails/rack/logger.rb:25:in `block in call'
activesupport (7.0.8) lib/active_support/tagged_logging.rb:99:in `block in tagged'
activesupport (7.0.8) lib/active_support/tagged_logging.rb:37:in `tagged'
activesupport (7.0.8) lib/active_support/tagged_logging.rb:99:in `tagged'
railties (7.0.8) lib/rails/rack/logger.rb:25:in `call'
sprockets-rails (3.4.2) lib/sprockets/rails/quiet_assets.rb:13:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/remote_ip.rb:93:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/request_id.rb:26:in `call'
rack (2.2.8) lib/rack/method_override.rb:24:in `call'
rack (2.2.8) lib/rack/runtime.rb:22:in `call'
activesupport (7.0.8) lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/server_timing.rb:61:in `block in call'
actionpack (7.0.8) lib/action_dispatch/middleware/server_timing.rb:26:in `collect_events'
actionpack (7.0.8) lib/action_dispatch/middleware/server_timing.rb:60:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/executor.rb:14:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/static.rb:23:in `call'
rack (2.2.8) lib/rack/sendfile.rb:110:in `call'
actionpack (7.0.8) lib/action_dispatch/middleware/host_authorization.rb:138:in `call'
railties (7.0.8) lib/rails/engine.rb:530:in `call'
puma (6.4.2) lib/puma/configuration.rb:272:in `call'
puma (6.4.2) lib/puma/request.rb:100:in `block in handle_request'
puma (6.4.2) lib/puma/thread_pool.rb:378:in `with_force_shutdown'
puma (6.4.2) lib/puma/request.rb:99:in `handle_request'
puma (6.4.2) lib/puma/server.rb:464:in `process_client'
puma (6.4.2) lib/puma/server.rb:245:in `block in run'
puma (6.4.2) lib/puma/thread_pool.rb:155:in `block in spawn_thread'

Impact

  • [x] High impact (It makes my app un-usable.)
  • [ ] Medium impact (I'm annoyed, but I'll live.)
  • [ ] Low impact (It's really a tiny thing that I could live with.)

I can't upgrade from avo 2.x to 3.x because of this issue.

Urgency

  • [x] High urgency (I can't continue development without it.)
  • [ ] Medium urgency (I found a workaround, but I'd love to have it fixed.)
  • [ ] Low urgency (It can wait. I just wanted you to know about it.)

iainbeeston avatar Jan 31 '24 16:01 iainbeeston

We have a similar issue that appeared when we upgraded from 3.0.1.beta19 to 3.1.0.

Upon upgrading, the actions disappeared on views where:

  • no main_panel block is defined, and
  • no top-level fields are visible

In these views, there are panel blocks containing visible fields. I would expect the actions to render since there are visible fields on the page, but they don't.

Minimal reproducible example (actions will not show):

class Avo::Resources::ExampleResource < Avo::BaseResource
  def fields
    panel name: 'Panel' do
      field :id, as: :text
    end
  end
end

Adding a main_panel block definition the fields block will cause the actions to show again.

Minimal reproducible example (actions will show):

class Avo::Resources::ExampleResource < Avo::BaseResource
  def fields
    main_panel {}

    panel name: 'Panel' do
      field :id, as: :text
    end
  end
end

We also found related unexpected behaviour with fields defined by blocks. If the block returns nil, but the field is "visible" from Avo's point of view (based on the visibility attributes, such as show_on), the actions are rendered even though the field is not. I would expect the actions not to render in this case.

Minimal reproducible example (actions will show):

class Avo::Resources::ExampleResource < Avo::BaseResource
  def fields
    field :id, as: :text do
      nil
    end
  end
end

osjjames avatar Feb 01 '24 16:02 osjjames

I'm also hitting this issue. Sorry I have no reproduction right now, just wanted to add my +1 to this and explain where i'm experiencing it.

I have a has_many field showing in a tab. It works fine but if I wrapp my fields in a main_panel so I can add a sidebar, I get the same error.

Without the main_panel it works perfectly.

rctneil avatar Feb 01 '24 22:02 rctneil

I recommend to start with a 5 minutes read of this docs page https://docs.avohq.io/3.0/resource-panels.html#resource-panels.

The Main Panel is the primary container for fields in a resource. It typically includes the resource's title, action buttons, and fields that are part of the resource's core data.

The Main Panel is automatically created by Avo based on your resource's field definitions.

There is this section that explains how do avo compute panels.

Basically the main_panel is computed and build by standalone fields (fields without panel).

Since associations fields and tabs do have their own panel and your resources have only panel-full items the main_panel is computed as empty so is not rendered and this behavior is intentional.

If you have a resource that computes the main_panel as empty and the "header" do not appear you can force it to be rendered by declaring main_panel do end wherever in your resource, before other panels, between them or even after them.

You may be thinking, why?

This allow so much more resource customization like:

  def fields
    tool Avo::ResourceTools::UserTool

    main_panel do end

    tabs id: :second_tabs_group do
      field :post, as: :has_one, name: "Main post"
      field :posts, as: :has_many, show_on: :edit
      field :comments, as: :has_many
      field :comment, as: :has_one, name: "Main comment"
    end
  end

image

OR

 def fields
    panel do
      field :email, as: :gravatar, link_to_record: true, as_avatar: :circle
      field :birthday, as: :date
      field :heading, as: :heading, label: ""
      field :active, as: :boolean, name: "Is active"

      sidebar panel_wrapper: false do
        tool Avo::ResourceTools::SidebarTool, render_panel: true
      end
    end

    tabs id: :second_tabs_group do
      field :post, as: :has_one, name: "Main post"
      field :posts, as: :has_many, show_on: :edit
      field :comments, as: :has_many
      field :comment, as: :has_one, name: "Main comment"
    end

    main_panel do end
  end

image

Tabs have their own independent panel (https://docs.avohq.io/3.0/tabs.html) and should not be defined inside a panel or main_panel that's why you're experiencing some errors. Try this instead:

def fields
  main_panel do end
  tabs { tab "foo" { field :bar, as: :text } }
end

OR

def fields
  tabs { tab "foo" { field :bar, as: :text } }
  main_panel do end
end

Let us know if there is something unclear or missing from the panel / tabs docs.

Paul-Bob avatar Feb 05 '24 08:02 Paul-Bob

Thanks @Paul-Bob that's a really thorough explanation and that's allowed me to fix the issue in my own app. I do have two questions about your answer though:

Tabs have their own independent panel (https://docs.avohq.io/3.0/tabs.html) and should not be defined inside a panel or main_panel that's why you're experiencing some errors.

That makes sense, but has_many fields also have their own independent panel and can be defined inside tabs. Why doesn't defining a has_many (panel) inside a tab (panel) cause a problem?

Also, if this is expected behaviour, should avo throw an error if you try to define a tab inside a panel? (Or every time you try to define a panel inside another panel) That would really help users to figure this out.

iainbeeston avatar Feb 05 '24 16:02 iainbeeston

This issue has been marked as stale because there was no activity for the past 15 days.

github-actions[bot] avatar Feb 29 '24 01:02 github-actions[bot]

Closing this because there was no activity for the past 15 days. Feel free to reopen if new information pops up ✌️

github-actions[bot] avatar Mar 15 '24 01:03 github-actions[bot]