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

Improve extendability of the new product page form #28752

Merged
merged 30 commits into from
Aug 10, 2022

Conversation

jolelievre
Copy link
Contributor

@jolelievre jolelievre commented Jun 14, 2022

Questions Answers
Branch? 8.0.x
Description? The new product page needs to be easily extendable by modules, so far we could already add some custom fields anywhere in the existing tabs but not add our own custom tab

To allow this a new NavigationTabType was introduced it basically allows you to display its children in a tabbed navigation each child represents a Tab This component is used as a base for the product form

Also in order to limit the twig content and especially the association of form theme a FormThemeExtension which introduces a new form_theme option that allows you to customize the form theme from the PHP form type code It's used on the whole product page which uses many sub form themes, but it will also allow modules to use their own form theme for their fields, or to customize the Core form theme if they need to

Finally for backward compatibility a custom ExtraModulesType form type was introduced to handle the integration of the displayAdminProductsExtra hook used in modules
Type? new feature
Category? BO
BC breaks? yes
Deprecations? no
Fixed ticket? Fix #28994
Related PRs You should use this PR for the example module PrestaShop/example-modules#50
How to test? See How to test section below
Possible impacts? IDs may have changed on some elements which could break some JS behaviour because the selectors wouldn't match anymore, that's why it'd be good to check that all fields work fine but not in a extensive way ^^ One edit, save, it's updated it's ok!

How to test

This PR changes the structure of the form a bit, so we need to check that the form behaves like before and that all the fields work as expected The idea is not to check everything extensively but only a quick run on each input to make sure they are not ignored because of some naming mismatch just to be sure Simply edit the field, save, the field is saved, done!

Also to ensure the extendability of modules works fine, this is the actual purpose of this PR, you should use the example module demoproductform and install it using the branch of this PR. Once it is installed it should add three new custom things in the product page:

Custom field in description tab

Capture d’écran 2022-06-30 à 16 21 16

Custom tab with a custom price field

Capture d’écran 2022-06-30 à 16 21 54

Configuration panel in the Modules tab

It is nearly a resume of the custom field used as a POC to show the feature is still usable for backward compatibility with modules relying on the displayAdminProductsExtra hook.

Capture d’écran 2022-06-30 à 16 22 32

Both fields should be editable and persisted in the DB, they are not used in FO right now only in the BO. If this PR is validated with the example module then it will also validate the module PR so we can merge it to provide an example for the community.

Finally, as all the PR for the product page V2 this PR MUST NOT be tested for multishop.

@prestonBot prestonBot added Feature Type: New Feature BC break Type: Introduces a backwards-incompatible break labels Jun 14, 2022
@jolelievre jolelievre force-pushed the tab-form-type branch 3 times, most recently from dd0bbfc to 9454b70 Compare June 29, 2022 17:48
@jolelievre jolelievre marked this pull request as ready for review June 30, 2022 13:25
@jolelievre jolelievre requested a review from a team as a code owner June 30, 2022 13:25
@prestonBot prestonBot added the 8.0.x Branch label Jun 30, 2022
@jolelievre jolelievre changed the title Introduce FormThemeExtension and NavigationTabType for product page form Improve extendability of the new product page form Jun 30, 2022
@matks matks added the migration symfony migration project label Jun 30, 2022
matks
matks previously approved these changes Jul 1, 2022
Copy link
Contributor

@matks matks left a comment

Choose a reason for hiding this comment

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

Ok for the code. I provided some suggestions. Are you sure the form still renders like before? You cleaned a lot of twig files

$title = $this->translator->trans('Help', [], 'Admin.Global');
}

$docLink = urlencode('https://help.prestashop.com/' . $this->legacyContext->getEmployeeLanguageIso() . '/doc/'
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice to have: no hardcoded URL domain?

Copy link
Contributor

Choose a reason for hiding this comment

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

Usage of http_build_query?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That could be an improvement indeed, I didn't look further and just copied the existing code from here

protected function generateSidebarLink($section, $title = false)
{
$version = $this->get('prestashop.core.foundation.version')->getSemVersion();
$legacyContext = $this->get('prestashop.adapter.legacy.context');
if (empty($title)) {
$title = $this->trans('Help', 'Admin.Global');
}
$docLink = urlencode('https://help.prestashop.com/' . $legacyContext->getEmployeeLanguageIso() . '/doc/'
. $section . '?version=' . $version . '&country=' . $legacyContext->getEmployeeLanguageIso());
return $this->generateUrl('admin_common_sidebar', [
'url' => $docLink,
'title' => $title,
]);
}

The purpose here was not to improve this code but put it in a reusable service. A refacto might be worth though and we could also refacto the generateSidebarLink so that it uses the new HelpProvider But I don't think it's in this PR's scope

@@ -157,7 +157,7 @@ protected function getColumns()
'route' => 'admin_products_v2_edit',
'route_param_name' => 'productId',
'route_param_field' => 'id_product',
'fragment' => 'tab-pricing-tab',
'fragment' => 'tab-product_pricing-tab',
Copy link
Contributor

Choose a reason for hiding this comment

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

question not really related to this pr, but why is it called fragment? (this is a fragment of what?)

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 was debated for a long time actually in this PR #22731 (comment) 😅

But TLDR; it's the naming used by Symfony so I didn't argue back in the days:
https://github.com/symfony/symfony/blob/4.4/src/Symfony/Component/Routing/Generator/UrlGenerator.php#L297-L311

If you think it should be changed and have a better suggestion I'm open though, it's now or never 😅

Copy link
Contributor

Choose a reason for hiding this comment

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

I wouldn't have question if we use the "fragment" in router (or other URL related service). But its too generic here because its Grid column, it doesn't relate directly with URL context. its like when we introduced route_param_name option in same Column definition - we didin't call it param_name. So I would suggest renaming this option to somewhat more specific, like url_fragment? route_fragment wdyt?

* form is automatic with a single form_row it is hard to define the form theme from twig that's one of the
* use cases of this extension.
*
* An addition option exist with this extension use_default_themes which is the equivalent of the only keyword
Copy link
Contributor

@zuk3975 zuk3975 Jul 18, 2022

Choose a reason for hiding this comment

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

Suggested change
* An addition option exist with this extension use_default_themes which is the equivalent of the only keyword
* Additional option supported by this extension - "use_default_themes" is the equivalent of the "only" keyword

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I rewrote the whole description a little with more detailed examples and a clearer explanation about the default theme from config. Let me know if it's enough or if I should detail some parts a bit more.

Comment on lines 61 to 62
* It is true by default, meaning to get the same behaviour as the twig keyword only you need to specify use_default_themes
* to false.
Copy link
Contributor

Choose a reason for hiding this comment

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

This sentence needs fixing (put commas where needed or so). Because by reading it, I can't understand which option means what 😄
Do I need to set it to false to "get the same behaviour as the twig keyword only" or `the true by default means the same behavior as the twig keyword "only"?

Copy link
Contributor

@zuk3975 zuk3975 Jul 18, 2022

Choose a reason for hiding this comment

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

smth like: Default value is "true" and it means the twig is using default global theme and your custom theme. Set it to "false" if you don't want to use the global theme at all

// Specify if default theme should be used as well
'use_default_themes' => true,
])
->setAllowedTypes('form_theme', ['null', 'string', 'array'])
Copy link
Contributor

@zuk3975 zuk3975 Jul 18, 2022

Choose a reason for hiding this comment

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

when you pass an array of form themes it uses them all?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it stacks them, I added a new example to explain this.

parent::buildForm($builder, $options);
if (!empty($options['toolbar_buttons'])) {
if ($builder->has('toolbar_buttons')) {
$this->logger->warning('You should not add a field which name is toolbar_buttons on this component as it is used internally.');
Copy link
Contributor

@zuk3975 zuk3975 Jul 18, 2022

Choose a reason for hiding this comment

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

Suggested change
$this->logger->warning('You should not add a field which name is toolbar_buttons on this component as it is used internally.');
$this->logger->warning('You should not add an option "toolbar_buttons" in this component as such option is already used internally.');

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, actually I'm not talking about an option here, but really a form field.

In the following lines, I'm doing $builder->add('toolbar_buttons', ...) meaning the form.tollbar_buttons is now a new field which type is ButtonCollectionType I need it to render it in the twig theme thus relying on Form component and twig tools.

But the field name is hard-coded here, meaning if a developer already used it then it will be overridden, that's why I check if the field already existed. I hesitated with an exception but I thought maybe it's a bit brutal, but maybe it would actually be easier to understand for the developer. Thus a clear exception is breaking everything instead of a field that magically disappears.

An alternative could be to throw an exception in debug mode and log a warning in prod mode. What do you think?

Copy link
Contributor

@matthieu-rolland matthieu-rolland Aug 4, 2022

Choose a reason for hiding this comment

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

that sounds like a good alternative 👍

also as we discussed, maybe adding an underscore like this _toolbar_buttons would reduce the risk of naming overlap

Copy link
Contributor

@zuk3975 zuk3975 left a comment

Choose a reason for hiding this comment

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

Couple comments (even though they are mostly about documentation wording, I think it must be adjusted, because it is hard to understand the meaning now. At least this one #28752 (comment))

Copy link
Contributor Author

@jolelievre jolelievre left a comment

Choose a reason for hiding this comment

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

Thanks for the feedback @zuk3975

I rebased the branch and improved the PHPDoc, I hope it's clearer now. Don't hesitate to tell me if there are still some complicated things to improve.

@@ -157,7 +157,7 @@ protected function getColumns()
'route' => 'admin_products_v2_edit',
'route_param_name' => 'productId',
'route_param_field' => 'id_product',
'fragment' => 'tab-pricing-tab',
'fragment' => 'tab-product_pricing-tab',
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 was debated for a long time actually in this PR #22731 (comment) 😅

But TLDR; it's the naming used by Symfony so I didn't argue back in the days:
https://github.com/symfony/symfony/blob/4.4/src/Symfony/Component/Routing/Generator/UrlGenerator.php#L297-L311

If you think it should be changed and have a better suggestion I'm open though, it's now or never 😅

* form is automatic with a single form_row it is hard to define the form theme from twig that's one of the
* use cases of this extension.
*
* An addition option exist with this extension use_default_themes which is the equivalent of the only keyword
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I rewrote the whole description a little with more detailed examples and a clearer explanation about the default theme from config. Let me know if it's enough or if I should detail some parts a bit more.

// Specify if default theme should be used as well
'use_default_themes' => true,
])
->setAllowedTypes('form_theme', ['null', 'string', 'array'])
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it stacks them, I added a new example to explain this.

parent::buildForm($builder, $options);
if (!empty($options['toolbar_buttons'])) {
if ($builder->has('toolbar_buttons')) {
$this->logger->warning('You should not add a field which name is toolbar_buttons on this component as it is used internally.');
Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, actually I'm not talking about an option here, but really a form field.

In the following lines, I'm doing $builder->add('toolbar_buttons', ...) meaning the form.tollbar_buttons is now a new field which type is ButtonCollectionType I need it to render it in the twig theme thus relying on Form component and twig tools.

But the field name is hard-coded here, meaning if a developer already used it then it will be overridden, that's why I check if the field already existed. I hesitated with an exception but I thought maybe it's a bit brutal, but maybe it would actually be easier to understand for the developer. Thus a clear exception is breaking everything instead of a field that magically disappears.

An alternative could be to throw an exception in debug mode and log a warning in prod mode. What do you think?

*/
class ExtraModulesType extends TranslatorAwareType
{
public const HOOK_NAME = 'displayAdminProductsExtra';
Copy link
Contributor

Choose a reason for hiding this comment

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

Nothing to change here, just wonder if we should pursue to use constants everywhere where using hooks (and why haven't we tried to do so before? like if we would have some static class to hold all the hooks or so?) or it sounds bad?
There are so many "magic strings" hanging now (i mean the hook names) everywhere 🤷‍♂️

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah probably for the core hooks at least it could make sense to have some consts, but it's not the purpose of this PR to refacto everything 😅 And it won't be applicable everywhere because we also have many dynamic hooks that are built with placeholders such as the object class names
But it's an idea to look into for future versions I guess

Copy link
Contributor

@matthieu-rolland matthieu-rolland left a comment

Choose a reason for hiding this comment

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

LGTM, minus the small improvement that could be made about potential naming overlap on toolbar_buttons

parent::buildForm($builder, $options);
if (!empty($options['toolbar_buttons'])) {
if ($builder->has('toolbar_buttons')) {
$this->logger->warning('You should not add a field which name is toolbar_buttons on this component as it is used internally.');
Copy link
Contributor

@matthieu-rolland matthieu-rolland Aug 4, 2022

Choose a reason for hiding this comment

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

that sounds like a good alternative 👍

also as we discussed, maybe adding an underscore like this _toolbar_buttons would reduce the risk of naming overlap

matks
matks previously approved these changes Aug 8, 2022
Copy link
Contributor

@matks matks left a comment

Choose a reason for hiding this comment

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

OK for me ✅

@@ -225,7 +225,7 @@ protected function getFilterSearchButtonSelector(): string
*/
protected function getCreateSubmitButtonSelector(): string
{
return 'product_create';
return 'create_product_create';
Copy link
Contributor

Choose a reason for hiding this comment

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

😄

@@ -1,4 +1,5 @@
{#**
<?php
Copy link
Contributor

Choose a reason for hiding this comment

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

Funny GitHub believes this is a conversion from twig to php

Copy link
Contributor

@khouloudbelguith khouloudbelguith left a comment

Choose a reason for hiding this comment

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

Hi @jolelievre,

Automatic tests => OK
I checked those tests:
Without module & With demo module installed:

  • Create a standard product V2 => OK
  • Create a product with combinations V2 => OK
  • Create a virtual product V2 => OK
  • Create a pack product V2 => OK

In the other case,

  • I tried to import products & edit them => All tabs are OK

  • I tried to add a product via API => OK

  • I tried to edit the product page with mobile => OK

  • I tried with RTL language => OK

But as discussed in slack, I have an exception when edit/add a product V2 when installing a custom module, I cannot publish it in public.

image

image

Without PR => ok
image

Thanks!

@khouloudbelguith khouloudbelguith added Waiting for author Status: action required, waiting for author feedback and removed Waiting for QA Status: action required, waiting for test feedback labels Aug 8, 2022
@jolelievre
Copy link
Contributor Author

Hi @khouloudbelguith

I managed to reproduce the bug with the module you provided. However, I have the same error even using the 8.0.x branch but only when I use PHP8.0. It doesn't come from this PR but from the module's code which uses this code implode($id_langs, ', ') but in PHP 8.0 the order of the parameters for this function were reversed so it should be implode(', ', $id_langs)

But anyway the PR is not responsible for this bug

Copy link
Contributor

@khouloudbelguith khouloudbelguith left a comment

Choose a reason for hiding this comment

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

Hi @jolelievre,

Thanks for your feedback.
As commented here: #28752 (review)

It is ok ✔️

Thanks!

@khouloudbelguith khouloudbelguith added QA ✔️ Status: check done, code approved and removed Waiting for author Status: action required, waiting for author feedback labels Aug 10, 2022
@prestonBot
Copy link
Collaborator

QA approved, well done! Message to the maintainers: do not forget to milestone it before the merge.

@khouloudbelguith khouloudbelguith added this to the 8.0.0 milestone Aug 10, 2022
@matks
Copy link
Contributor

matks commented Aug 10, 2022

Thank you @jolelievre

@matks matks merged commit 435dc79 into PrestaShop:8.0.x Aug 10, 2022
@matks matks mentioned this pull request Aug 10, 2022
6 tasks
@jolelievre jolelievre deleted the tab-form-type branch October 17, 2022 17:38
@kpodemski kpodemski added Documentation ✔️ Developer documentation is up-to-date and removed Needs documentation Needs an update of the developer documentation labels Jun 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
8.0.x Branch BC break Type: Introduces a backwards-incompatible break Documentation ✔️ Developer documentation is up-to-date Feature Type: New Feature migration symfony migration project QA ✔️ Status: check done, code approved
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Extend the product page
9 participants