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

Customer service controller migration (grid part) #29732

Merged

Conversation

margud
Copy link
Contributor

@margud margud commented Sep 23, 2022

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 image

@margud margud requested a review from a team as a code owner September 23, 2022 12:50
@prestonBot prestonBot added the Refactoring Type: Refactoring label Sep 23, 2022
@prestonBot prestonBot added the develop Branch label Oct 19, 2022
Copy link
Contributor

@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 @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 {
Copy link
Contributor

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);
Copy link
Contributor

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);
Copy link
Contributor

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);
}
}
Copy link
Contributor

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:

class CustomizationFieldRepository extends AbstractObjectModelRepository

use PrestaShop\PrestaShop\Core\Domain\CustomerService\CommandHandler\BulkDeleteCustomerThreadHandlerInterface;
use PrestaShop\PrestaShop\Core\Domain\CustomerService\Exception\CustomerThreadNotFoundException;

class BulkDeleteCustomerThreadHandler implements BulkDeleteCustomerThreadHandlerInterface
Copy link
Contributor

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:

Suggested change
class BulkDeleteCustomerThreadHandler implements BulkDeleteCustomerThreadHandlerInterface
/**
* Handles command for customer thread bulk deletion using legacy object model
*/
class BulkDeleteCustomerThreadHandler implements BulkDeleteCustomerThreadHandlerInterface

Comment on lines 180 to 190
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;
}
Copy link
Contributor

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?

Copy link
Contributor

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?

*
* @return RedirectResponse
*/
public function updateStatusAction($customerThreadId, $newStatus)
public function updateStatusAction(int $customerThreadId, Request $request)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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:
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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:
Copy link
Contributor

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)
Copy link
Contributor

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

$this->customerThreadRepository = $customerThreadRepository;
}

public function handle(BulkDeleteCustomerThreadCommand $command)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public function handle(BulkDeleteCustomerThreadCommand $command)
public function handle(BulkDeleteCustomerThreadCommand $command): void

*/
interface BulkDeleteCustomerThreadHandlerInterface
{
public function handle(BulkDeleteCustomerThreadCommand $command);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public function handle(BulkDeleteCustomerThreadCommand $command);
public function handle(BulkDeleteCustomerThreadCommand $command): void;

*/
interface DeleteCustomerThreadHandlerInterface
{
public function handle(DeleteCustomerThreadCommand $command);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public function handle(DeleteCustomerThreadCommand $command);
public function handle(DeleteCustomerThreadCommand $command): void;


use Contact;

class ContactTypeProvider
Copy link
Contributor

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)
Copy link
Contributor

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.

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.

just the concern about the form choice provider and some missing return types.

zuk3975
zuk3975 previously approved these changes Oct 27, 2022
Copy link
Contributor

@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 @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
Copy link
Contributor

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
class CustomerThreadStatusesChoiceProvider implements ConfigurableFormChoiceProviderInterface
class CustomerThreadStatusesChoiceProvider implements FormChoiceProviderInterface

Copy link
Contributor Author

Choose a reason for hiding this comment

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

well when i change it to formChoice i get this error:
image

Copy link
Contributor

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 = [])
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public function getChoices(array $options = [])
public function getChoices()

Copy link
Contributor Author

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

Copy link
Contributor

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

@@ -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:
Copy link
Contributor

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:
Copy link
Contributor

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)

Copy link
Contributor

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.

Copy link
Contributor

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')
Copy link
Contributor

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:
Capture d’écran 2022-10-27 à 15 09 29
Capture d’écran 2022-10-27 à 15 09 18

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)

Copy link
Contributor

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

Copy link
Contributor

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:
Copy link
Contributor

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

Copy link
Contributor Author

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

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 guess i can move them from adapter to core aswell, then it should be in choice_provider.yml

Copy link
Contributor

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 😅

Copy link
Contributor

@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 @margud

Since @MatShir answered there is just a little bug about the message column to handle. And one service file to move in a subfolder and we're good!

{
$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')
Copy link
Contributor

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:
Copy link
Contributor

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.

Copy link
Contributor

@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 @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) }}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
{{ record[column.options.field]|slice(0, column.options.max_displayed_characters) }}
{{ record[column.options.field]|slice(0, column.options.max_displayed_characters) }}...

Copy link
Contributor

Choose a reason for hiding this comment

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

or maybe better

Suggested change
{{ record[column.options.field]|slice(0, column.options.max_displayed_characters) }}
{{ record[column.options.field]|slice(0, column.options.max_displayed_characters) }}…

Copy link
Contributor

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:
Copy link
Contributor

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

Copy link
Contributor

@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.

Just one little bug to fix


{{ record[column.options.field] }}
{% if column.options.max_displayed_characters > 0 %}
Copy link
Contributor

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
{% if column.options.max_displayed_characters > 0 %}
{% if column.options.max_displayed_characters > 0 and column.options.field|length > column.options.max_displayed_characters %}

Copy link
Contributor

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 @@
{#**
{# **
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
{# **
{#**

@@ -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)
*#}
* #}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* #}
*#}

Copy link
Contributor

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

Copy link
Contributor

@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 @margud

@jolelievre jolelievre merged commit 91395e8 into PrestaShop:develop Nov 3, 2022
@jolelievre
Copy link
Contributor

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

@kpodemski kpodemski added this to the 8.1.0 milestone Feb 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
develop Branch Refactoring Type: Refactoring
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Migrate "Customer Service > Customer Service" dashboard page
6 participants