-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Customer service controller migration (grid part) #29732
Customer service controller migration (grid part) #29732
Conversation
margud
commented
Sep 23, 2022
•
edited
Loading
edited
Questions | Answers |
---|---|
Branch? | develop |
Description? | customer service migration. Grid part |
Type? | refacto |
Category? | BO |
BC breaks? | no |
Fixed ticket? | Fixes #16592 |
How to test? | opening sell/customer-service/customer-threads in bo you will see new customer service grid. Grid, view and deletion should already work. Also filter and pagination is added. |
Image |
src/PrestaShopBundle/Resources/config/routing/admin/sell/customer_service/customer_threads.yml
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @margud
Great (and fast!) work! Just a few things to improve, when the PR is approved I think I'll perform a quick QA by dev and negotiate with QA team so that we can merge it and only ask them to test it when it'll be fully finished.
@@ -0,0 +1,6 @@ | |||
#customer_thread_grid { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure it's worth splitting this into two files considering the amount of code 😅
But it's a detail!
} | ||
|
||
if (!$customerThread->delete()) { | ||
throw new CustomerThreadNotFoundException(sprintf('Cannot delete customer thread with id "%d"', $customerThreadId->getValue()), CustomerThreadNotFoundException::FAILED_DELETE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure the exception is adapted in this case, it should rather be a CannotDeleteCustomerThreadException
the entity does exist since you already checked this beforehand So if the deletion fails it's because of another problem, so another type of exception
} | ||
|
||
if (!$customerThread->delete()) { | ||
throw new CustomerThreadNotFoundException(sprintf('Cannot delete customer thread with id "%d"', $command->getCustomerThreadId()->getValue()), CustomerThreadNotFoundException::FAILED_DELETE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same suggestion as the bulk handler
if (!$customerThread->delete()) { | ||
throw new CustomerThreadNotFoundException(sprintf('Cannot delete customer thread with id "%d"', $command->getCustomerThreadId()->getValue()), CustomerThreadNotFoundException::FAILED_DELETE); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also now that you are more used to CQRS and migrating pages, I think you could implement this by relying on our repository classes. You'd need to create a CustomerThreadRepository
that extends AbstractObjectModelRepository
it provides a few helper methods that will help you create the methods you need.
Having a dedicated repository removes the intelligence from the handler into dedicated services that can be used in other context and it prevents duplicating code, it also adds more consistency since all the usual methods are made the same.
Here is an example of implementation:
PrestaShop/src/Adapter/Product/Customization/Repository/CustomizationFieldRepository.php
Line 44 in 5c51e4c
class CustomizationFieldRepository extends AbstractObjectModelRepository |
use PrestaShop\PrestaShop\Core\Domain\CustomerService\CommandHandler\BulkDeleteCustomerThreadHandlerInterface; | ||
use PrestaShop\PrestaShop\Core\Domain\CustomerService\Exception\CustomerThreadNotFoundException; | ||
|
||
class BulkDeleteCustomerThreadHandler implements BulkDeleteCustomerThreadHandlerInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should always describe the classes you create, even if it's a short description it gives more context to developers who are not necessarily used to the PrestaShop architecture (contributors, new developers on the project, ...). Since PrestaShop is a CMS/framework it's important to document the code.
For handlers which are based on legacy object usually we describe like this:
class BulkDeleteCustomerThreadHandler implements BulkDeleteCustomerThreadHandlerInterface | |
/** | |
* Handles command for customer thread bulk deletion using legacy object model | |
*/ | |
class BulkDeleteCustomerThreadHandler implements BulkDeleteCustomerThreadHandlerInterface |
if (in_array($filterName, ['ct.id_customer_thread', 'ct.private'])) { | ||
$builder->andWhere('z.' . $filterName . ' = :' . $filterName); | ||
$builder->setParameter($filterName, $filterValue); | ||
continue; | ||
} | ||
|
||
if (in_array($filterName, ['message', 'private'])) { | ||
$builder->andWhere('cm.' . $filterName . ' LIKE :' . $filterName); | ||
$builder->setParameter($filterName, '%' . $filterValue . '%'); | ||
continue; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like private is handled twice and differently no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Besides I believe there is no table that is aliased to z
, and ['ct.id_customer_thread', 'ct.private']
are not in the allowed filters so the block should never be reached right?
src/PrestaShopBundle/Resources/config/routing/admin/sell/customer_service/customer_threads.yml
Show resolved
Hide resolved
* | ||
* @return RedirectResponse | ||
*/ | ||
public function updateStatusAction($customerThreadId, $newStatus) | ||
public function updateStatusAction(int $customerThreadId, Request $request) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public function updateStatusAction(int $customerThreadId, Request $request) | |
public function updateStatusFromViewAction(int $customerThreadId, Request $request) |
I suggest renaming this controller to understand that two controllers are needed because they don't redirect to the same place based on the context they have been used
@@ -19,7 +38,7 @@ admin_customer_threads_reply: | |||
customerThreadId: \d+ | |||
|
|||
admin_customer_threads_update_status: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
admin_customer_threads_update_status: | |
admin_customer_threads_view_update_status: |
@@ -34,3 +34,24 @@ services: | |||
tags: | |||
- name: tactician.handler | |||
command: 'PrestaShop\PrestaShop\Core\Domain\CustomerService\Command\ReplyToCustomerThreadCommand' | |||
|
|||
prestashop.adapter.customer_service.command_handler.delete_customer_thread_handler: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As suggested in a previous comment all those new handlers could be added to the core namespace instead
Maybe some others that you created recently could also be moved, but it should be handled in another PR
* | ||
* @throws CustomerServiceException | ||
*/ | ||
public function setCustomerThreadIds(array $customerThreadIds) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return void, or I guess even bettter - self to make it fluent setter
src/Core/Domain/CustomerService/Command/BulkDeleteCustomerThreadCommand.php
Show resolved
Hide resolved
$this->customerThreadRepository = $customerThreadRepository; | ||
} | ||
|
||
public function handle(BulkDeleteCustomerThreadCommand $command) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public function handle(BulkDeleteCustomerThreadCommand $command) | |
public function handle(BulkDeleteCustomerThreadCommand $command): void |
*/ | ||
interface BulkDeleteCustomerThreadHandlerInterface | ||
{ | ||
public function handle(BulkDeleteCustomerThreadCommand $command); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public function handle(BulkDeleteCustomerThreadCommand $command); | |
public function handle(BulkDeleteCustomerThreadCommand $command): void; |
*/ | ||
interface DeleteCustomerThreadHandlerInterface | ||
{ | ||
public function handle(DeleteCustomerThreadCommand $command); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public function handle(DeleteCustomerThreadCommand $command); | |
public function handle(DeleteCustomerThreadCommand $command): void; |
|
||
use Contact; | ||
|
||
class ContactTypeProvider |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't it a simple form choice provider as others?
Even if the select form is in grid, I think its still ok to have this under the same directory as other form choice providers
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getData(SearchCriteriaInterface $searchCriteria) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess there is no return type, because of the old interface, but Im pretty sure you could add the return type in this class and it would work 🤔 (It breaks only when interface defines different return type, but it works if its not defined at all). Same idea for the PrivateColumn.php above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just the concern about the form choice provider and some missing return types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @margud
Just a few minor things to fix and it's all good
However, you didn't answer my question about the complex subquery, can you explain this a bit more and maybe add a comment if it is really required? Because I feel like the subquery is overly complex, and also it seems like the same thread can be returned multiple times (see my comments for more details).
use PrestaShop\PrestaShop\Core\Form\ConfigurableFormChoiceProviderInterface; | ||
use Symfony\Contracts\Translation\TranslatorInterface; | ||
|
||
class CustomerThreadStatusesChoiceProvider implements ConfigurableFormChoiceProviderInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you don't need to pass any options or parameters then the FormChoiceProviderInterface
is enough
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
class CustomerThreadStatusesChoiceProvider implements ConfigurableFormChoiceProviderInterface | |
class CustomerThreadStatusesChoiceProvider implements FormChoiceProviderInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah because you probably didn't update the grid factory that relies on it
$this->translator = $translator; | ||
} | ||
|
||
public function getChoices(array $options = []) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public function getChoices(array $options = []) | |
public function getChoices() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can't do that because of interface
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's of course once you change the interface
src/Core/Domain/CustomerService/Command/BulkDeleteCustomerThreadCommand.php
Show resolved
Hide resolved
@@ -2,8 +2,8 @@ services: | |||
_defaults: | |||
public: true | |||
|
|||
prestashop.adapter.customer_message.command_handler.add_order_customer_message: | |||
class: PrestaShop\PrestaShop\Adapter\CustomerService\CommandHandler\AddOrderCustomerMessageHandler | |||
prestashop.core.domain.customer_service.command_handler.add_order_customer_message: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, this one (along with all the other moved handlers) should now be placed into https://github.com/PrestaShop/PrestaShop/blob/develop/src/PrestaShopBundle/Resources/config/services/core/domain/customer_service.yml
@@ -0,0 +1,19 @@ | |||
services: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file should be moved into src/PrestaShopBundle/Resources/config/services/core/domain/customer_service.yml
instead
For the handler definitions, we put them in the domain subfolder (the file already exists by the way)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again the handlers service definition should be in the domain
subfolder to separate them from the "regular" services (like repositories, updater, modifier, ...) which are potentially used by the handlers themselves.
But since the definition of CQRS handlers is quite verbose it's better to keep them separated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file still needs to be moved in domain subfolder
{ | ||
$qb = $this->getQueryBuilder($searchCriteria) | ||
->select('ct.*, CONCAT(c.`firstname`," ",c.`lastname`) as customer') | ||
->addSelect('cm.message, cm.private, cl.name as contact, l.name as langName') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed a different between the migrated grid and the legacy one regarding message column:
Before it displayed all messages (but truncated) now it displays only the first message. I'm not sure either of these behaviour is correct IMHO, I think it should only display the last message (truncated as well of course)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The last message truncated is enough IMO as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, thanks @MatShir then you'll have to update the specs or the QA won't accept this change.
So @margud you need to add an orderBy condition to get the latest message in jour join. About the truncate maybe it should be handled in the grid rendering but I don't know if we have a column type that handles this, it could be interesting I think. Or maybe even better we can add an option to DataColumn
max_displayed_characters
or something, and handle it in the twig This way e can reuse it in any other grid.
@@ -93,3 +93,8 @@ services: | |||
- '@prestashop.adapter.attribute.repository.attribute_repository' | |||
- '@prestashop.core.product.combination.name_builder.combination_name_builder' | |||
- '@=service("prestashop.adapter.legacy.context").getLanguage().id' | |||
|
|||
prestashop.adapter.form.choice_provider.contact_type_choice_provider: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This definition should be in the choice_provider.yml
file, it's not a form type it's a choice provider
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this one is form choice provider
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i guess i can move them from adapter to core aswell, then it should be in choice_provider.yml
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nearly all choice provider are used by form types actually, but we still separate them because we have a lot of choice providers and a lot of form types So we try to keep things (a bit) clean 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{ | ||
$qb = $this->getQueryBuilder($searchCriteria) | ||
->select('ct.*, CONCAT(c.`firstname`," ",c.`lastname`) as customer') | ||
->addSelect('cm.message, cm.private, cl.name as contact, l.name as langName') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, thanks @MatShir then you'll have to update the specs or the QA won't accept this change.
So @margud you need to add an orderBy condition to get the latest message in jour join. About the truncate maybe it should be handled in the grid rendering but I don't know if we have a column type that handles this, it could be interesting I think. Or maybe even better we can add an option to DataColumn
max_displayed_characters
or something, and handle it in the twig This way e can reuse it in any other grid.
@@ -0,0 +1,19 @@ | |||
services: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again the handlers service definition should be in the domain
subfolder to separate them from the "regular" services (like repositories, updater, modifier, ...) which are potentially used by the handlers themselves.
But since the definition of CQRS handlers is quite verbose it's better to keep them separated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @margud
Only two small things to fix and I think we're good for a first merge of this initial PR
|
||
{{ record[column.options.field] }} | ||
{% if column.options.max_displayed_characters > 0 %} | ||
{{ record[column.options.field]|slice(0, column.options.max_displayed_characters) }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{{ record[column.options.field]|slice(0, column.options.max_displayed_characters) }} | |
{{ record[column.options.field]|slice(0, column.options.max_displayed_characters) }}... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or maybe better
{{ record[column.options.field]|slice(0, column.options.max_displayed_characters) }} | |
{{ record[column.options.field]|slice(0, column.options.max_displayed_characters) }}… |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But we need to show that the content was truncated
@@ -0,0 +1,19 @@ | |||
services: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file still needs to be moved in domain subfolder
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just one little bug to fix
|
||
{{ record[column.options.field] }} | ||
{% if column.options.max_displayed_characters > 0 %} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just tested it and the ellipsis is always shown I think a condition is missing here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{% if column.options.max_displayed_characters > 0 %} | |
{% if column.options.max_displayed_characters > 0 and column.options.field|length > column.options.max_displayed_characters %} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about |length
filter, but something similar ^^
@@ -1,4 +1,4 @@ | |||
{#** | |||
{# ** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{# ** | |
{#** |
@@ -21,6 +21,10 @@ | |||
* @author PrestaShop SA and Contributors <[email protected]> | |||
* @copyright Since 2007 PrestaShop SA and Contributors | |||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) | |||
*#} | |||
* #} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* #} | |
*#} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, don't add spaces here, or I believe our prestashop:licenses:update
command will not detect the license correctly and it will add another one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @margud
Thanks @margud I merged this PR without QA because it's only part of the page, a few other PRs are needed to finish it and the last one will be tested by QA team |