Skip to content

Sending to nextcloud groups#810

Merged
ChristophWurst merged 4 commits into
nextcloud:masterfrom
myrho:autocomplete-groups-i
Mar 26, 2018
Merged

Sending to nextcloud groups#810
ChristophWurst merged 4 commits into
nextcloud:masterfrom
myrho:autocomplete-groups-i

Conversation

@myrho

@myrho myrho commented Feb 27, 2018

Copy link
Copy Markdown
Collaborator

This PR overrides #800.

  • Autocomplete nextcloud groups in recipient input fields.
  • Prefix nextcloud group recipients with "nextcloud:" to have a means of distinguishing from yet-to-implement Contacts groups recipients (Sending to groups [$150 awarded] #41).
  • Expands groups to email addresses of group members.

Fixes #41

@ChristophWurst ChristophWurst left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

Comment thread lib/AppInfo/Application.php Outdated
$container->registerAlias('ErrorMiddleware', ErrorMiddleware::class);
$container->registerMiddleWare('ErrorMiddleware');

$container->registerService('OCA\Mail\Service\GroupsIntegration', function($c) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think you have to register this here. The DI container should be able to resolve this class automatically.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

How should the container know which AbstractGroupServices GroupsIntegration should be constructed with?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

See below. Use an interface instead of this abstract class and register the implementation as alias of the interface here (see other services in this file).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please use $container->registerAlias(IGroupService::class, NextcloudGroupService::class); instead


public function findMatches($term) {
$recipientsFromContacts = $this->contactsIntegration->getMatchingRecipient($term);
$recipientGroups = $this->groupsIntegration->getMatchingGroups($term);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd prefer to have this logic moved to a service class, maybe even introduce a facade for that. But that's a minor enhancement - this can be done later on 😉


namespace OCA\Mail\Service\Group;

abstract class AbstractGroupService {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks like an interface would be the better choice here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes!


return array_map(
function($g) {
return [

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks like a good candidate to implement PHP's JsonSerializable interface for this.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I ported this pattern from Contacts\IManager::search. It's just returning key/value pairs. Where would JsonSerializable fit in?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nvm, just realized that $g is some data structure from the Nextcloud server, so nothing we can change. In that case that's fine 👍

$users = $this->groupManager->get($groupId)->getUsers();
return array_map(
function($user) {
return [

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

same here

Comment thread lib/Service/GroupsIntegration.php Outdated
*/
private $groupServices = [];

public function __construct(AbstractGroupService ...$groupServices) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For simplicity, I'd recommend to only inject a single group service (there's only one so far). You don't have to use the concrete impl here but use the interface (as proposed above) and register the impl as alias for the interface in Application.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I designed this so it will be extensible for Contacts groups.
But I could reserve this feature for later when Contacts groups integration will be ready.

/**
* The services to get groups from
*
* @var IGroupService[]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this interface really exist?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It did :-)
Missed to update the docs here.

But AbstractGroupService should become IGroupService again anyways.

Comment thread tests/Service/GroupsIntegrationTest.php Outdated
protected function setUp() {
parent::setUp();

$this->groupService1 = $this->getMockBuilder(NextcloudGroupService::class)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

use $this->createMock(...::class);.

*
* @return string
*/
abstract public function getNamespace();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we really need these namespaces? This kinda looks like an unnecessary generalization.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, I think so. There are two reasons:

  1. When GroupIntegration once would be extended for Contacts groups, we need a means to tell apart whether it's a Nextcloud or a Contacts group, since both applications can have equally named groups. Eg. nextcloud:mygroup vs. contacts:mygroup.
  2. We need to distinguish nextcloud groups from single recipients, since Nextcloud groups could look like email addresses. Eg. nextcloud:group@domain.com vs. group@domain.com ...

$this->appName = 'mail';
$this->request = $this->createMock(IRequest::class);
$this->accountService = $this->createMock(AccountService::class);
$this->groupsIntegration = new GroupsIntegration();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

use a mock instead please

@ChristophWurst ChristophWurst left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🚀

Comment thread lib/AppInfo/Application.php Outdated
$container->registerAlias('ErrorMiddleware', ErrorMiddleware::class);
$container->registerMiddleWare('ErrorMiddleware');

$container->registerService('OCA\Mail\Service\GroupsIntegration', function($c) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please use $container->registerAlias(IGroupService::class, NextcloudGroupService::class); instead

protected function setUp() {
parent::setUp();

$this->groupsManager = $this->createMock('OCP\IGroupManager');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

use the ::class notation


public function getUsers($groupId) {
if(!$this->groupManager->groupExists($groupId)) {
throw new \Exception("$groupId ({$this->getNamespace()}) does not exist");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

throw a OCA\Mail\Exception\ServiceException instead

Comment thread lib/Service/GroupsIntegration.php Outdated
*/
public function servicePrefix(IGroupService $gs) {
if(empty($gs->getNamespace())) {
throw new \Exception('GroupService has no namespace');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

use ServiceException instead

$this->contactsIntegration = $this->getMockBuilder('\OCA\Mail\Service\ContactsIntegration')
->disableOriginalConstructor()
->getMock();
$this->groupsIntegration = $this->getMockBuilder('\OCA\Mail\Service\GroupsIntegration')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

->createMock and the ::class notation

}

public function testGetUsersWrong() {
$this->expectException(\Exception::class);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

needs update to ServiceException

Comment thread lib/Service/GroupsIntegration.php Outdated
*/
private $groupServices = [];

public function __construct(IGroupService ...$groupServices) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd prefer to simply inject a single instance as long as we have only a single implementation. Other implementations can be injected later.

Comment thread tests/Service/GroupsIntegrationTest.php Outdated
}

public function testExpandEmpty() {
$this->expectException(\Exception::class);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ServiceException

Comment thread tests/Service/GroupsIntegrationTest.php Outdated
}

public function testExpandWrong2() {
$this->expectException(\Exception::class);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ServiceException

@ChristophWurst ChristophWurst left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

👍 @myrho thanks a lot! I'll try to take over and integrate this PR next week

Comment thread lib/Service/Group/IGroupService.php Outdated

}

?>

@ChristophWurst ChristophWurst Mar 13, 2018

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

  • remove closing tag

@ChristophWurst ChristophWurst self-assigned this Mar 13, 2018
@ChristophWurst ChristophWurst force-pushed the autocomplete-groups-i branch from 92ab7e7 to 539966a Compare March 19, 2018 08:21
@ChristophWurst ChristophWurst force-pushed the autocomplete-groups-i branch from 2d2c7b6 to 1aa8a4d Compare March 19, 2018 08:55

@ChristophWurst ChristophWurst left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Squashed the commits, rebased onto current master and fixed some coding style issues. Code looks good and manual tests confirmed that the feature works as expected. There are a few edge cases that are not handled well (like groups that have no members with email addresses set - horde will throw an exception because there is no recipient).

@myrho that can be fixed in follow-up pull requests. Thanks a lot for your work on this

@ChristophWurst

Copy link
Copy Markdown
Member

Scrutinizer — 3 new issues

Should be addressed before merging.

@ChristophWurst ChristophWurst left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Scrutinizer is happy now 😉

@ChristophWurst

Copy link
Copy Markdown
Member

cc @nextcloud/mail for review. Would be great to have a second person testing/reviewing this new feature

@ChristophWurst

Copy link
Copy Markdown
Member

cc @nextcloud/mail for review. Would be great to have a second person testing/reviewing this new feature

ain't nobody got time for that.

Merging.

@ChristophWurst ChristophWurst merged commit f55185a into nextcloud:master Mar 26, 2018
@ChristophWurst

Copy link
Copy Markdown
Member

@myrho please don't forget to claim your well-deserved bounty for fixing #41 💰

@alexanderdd

Copy link
Copy Markdown

I am confused.

Please see my comment in #41

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.

Sending to groups [$150 awarded]

3 participants