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

feat(CO-727): allow Save To Files from a shared mailbox #255

Merged
merged 25 commits into from
Jun 26, 2023

Conversation

frisonisland
Copy link
Contributor

@frisonisland frisonisland commented Jun 16, 2023

This PR expands the Save-To-Files by enabling the feature from a shared mailbox.

Changes:

- add test to check shared mailbox is used to search for attachment
- split messageId in UUID:messageId
- make test variables more idiomatic
- add map instead of flatMap to avoid nullpointer exception in case of null response from Files client
- Cannot use latest mock server version because of jackson compatibility
- Tests fail with missing com/fasterxml/jackson/databind/DeserializationFeature.FAIL_ON_TRAILING_TOKENS with latest version of mock server
- remove verify method call test since there is already a similar test
- simplify implementation using vavr For
- use vavr try withresources
- use should-when syntax
Using mockserver 5.13.0+ is not possible for jackson compatibility issues.
@frisonisland frisonisland requested review from keshavbhatt and Polpetta and removed request for keshavbhatt June 19, 2023 08:14
Copy link
Member

@keshavbhatt keshavbhatt left a comment

Choose a reason for hiding this comment

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

I made few suggestions to improve code quality.

Please use recoverWith instead of mapFailure (as it adds additional overhead in some places where not required) in other places as well where possible. I just pointed out once instance, but saw usage in other places.

* JUnit5 does not require methods to be public
* Use recoverWith to improve readability
* use mapFailure when needed (multiple cases)
* Add ServiceException "internal error"
* Add more tests on possible failures of CopyToFiles
@frisonisland frisonisland requested a review from keshavbhatt June 19, 2023 13:56
Copy link
Member

@keshavbhatt keshavbhatt left a comment

Choose a reason for hiding this comment

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

LGTM.

I don't recommend the current exception propagation approach used in this codebase because it makes it difficult to track them in the calling function. I think it can be suitable in certain scenarios where the exceptions are not critical and can be disregarded.

Additionally, I suggest avoiding excessive use of functional chaining as it makes the code appear messy and unorganized.

@frisonisland
Copy link
Contributor Author

LGTM.

I don't recommend the current exception propagation approach used in this codebase because it makes it difficult to track them in the calling function. I think it can be suitable in certain scenarios where the exceptions are not critical and can be disregarded.

Additionally, I suggest avoiding excessive use of functional chaining as it makes the code appear messy and unorganized.

I see this class as a Controller, that's why I decided to map the failure on each validation step (check token, check body, check messageId, check file exists, etc.). Then all is chained in the handle method
Unfortunately about functional I cannot say much, some people like it more, some not (aside other technical benefits).
This is also how vavr works.
I think we should opt for a common decision that works for us.

@Polpetta
Copy link
Contributor

Additionally, I suggest avoiding excessive use of functional chaining as it makes the code appear messy and unorganized.

Sorry, but I'm completely opposite to this statement. Please do use more functional chaining as it makes the code more maintainable by making it declarative

Functional seems scary at the beginning, but once it enters in your mind and you understand how it works it is a breeze to your programming experience.

@keshavbhatt
Copy link
Member

Sorry, but I'm completely opposite to this statement.

I dont have issues with employing funtional apparoach to write the code. The issue I was trying to point out is "excessive use of functional chaining" that we can see in this code. That is making the code hard to debug and read.

"The code appear messy and unorganized"

I mean ⤵️

  final NodeId nodeId =
       Optional.ofNullable(
               getRequestObject(request)
                   .flatMap(
                       copyToFilesRequest ->
                           this.getAttachmentToCopy(copyToFilesRequest, zsc)
                               .flatMap(
                                   attachment ->
                                       Try.withResources(attachment::getInputStream)
                                           .of(
                                               inputStream ->
                                                   For(
                                                           Try.of(
                                                               () ->
                                                                   ZimbraCookie
                                                                           .COOKIE_ZM_AUTH_TOKEN
                                                                       + "="
                                                                       + zsc.getAuthToken()
                                                                           .getEncoded()),
                                                           this.getDestinationFolderId(
                                                               copyToFilesRequest),
                                                           this.getAttachmentName(attachment),
                                                           this.getAttachmentContentType(
                                                               attachment),
                                                           Try.withResources(
                                                                   attachment::getInputStream)
                                                               .of(this::getAttachmentSize)
                                                               .flatMap(identity()))
                                                       .yield(
                                                           (cookie,
                                                               folderId,
                                                               fileName,
                                                               contentType,
                                                               fileSize) ->
                                                               filesClient.uploadFile(
                                                                   cookie,
                                                                   folderId,
                                                                   fileName,
                                                                   contentType,
                                                                   inputStream,
                                                                   fileSize)))))
                   .flatMap(identity())
                   .flatMap(identity())
                   .mapFailure(
                       Case(
                           $(ex -> !(ex instanceof ServiceException)),
                           ServiceException::INTERNAL_ERROR))
                   .get())
           .orElseThrow(() -> ServiceException.FAILURE("got null response from Files server."));

@frisonisland
Copy link
Contributor Author

Sorry, but I'm completely opposite to this statement.

I dont have issues with employing funtional apparoach to write the code. The issue I was trying to point out is "excessive use of functional chaining" that we can see in this code. That is making the code hard to debug and read.

"The code appear messy and unorganized"

I mean arrow_heading_down

  final NodeId nodeId =
       Optional.ofNullable(
               getRequestObject(request)
                   .flatMap(
                       copyToFilesRequest ->
                           this.getAttachmentToCopy(copyToFilesRequest, zsc)
                               .flatMap(
                                   attachment ->
                                       Try.withResources(attachment::getInputStream)
                                           .of(
                                               inputStream ->
                                                   For(
                                                           Try.of(
                                                               () ->
                                                                   ZimbraCookie
                                                                           .COOKIE_ZM_AUTH_TOKEN
                                                                       + "="
                                                                       + zsc.getAuthToken()
                                                                           .getEncoded()),
                                                           this.getDestinationFolderId(
                                                               copyToFilesRequest),
                                                           this.getAttachmentName(attachment),
                                                           this.getAttachmentContentType(
                                                               attachment),
                                                           Try.withResources(
                                                                   attachment::getInputStream)
                                                               .of(this::getAttachmentSize)
                                                               .flatMap(identity()))
                                                       .yield(
                                                           (cookie,
                                                               folderId,
                                                               fileName,
                                                               contentType,
                                                               fileSize) ->
                                                               filesClient.uploadFile(
                                                                   cookie,
                                                                   folderId,
                                                                   fileName,
                                                                   contentType,
                                                                   inputStream,
                                                                   fileSize)))))
                   .flatMap(identity())
                   .flatMap(identity())
                   .mapFailure(
                       Case(
                           $(ex -> !(ex instanceof ServiceException)),
                           ServiceException::INTERNAL_ERROR))
                   .get())
           .orElseThrow(() -> ServiceException.FAILURE("got null response from Files server."));

TBH it's just the "For" that makes the code messy, but the rest is quite straightforward

@frisonisland
Copy link
Contributor Author

frisonisland commented Jun 22, 2023

"The code appear messy and unorganized"

I mean arrow_heading_down

  final NodeId nodeId =
       Optional.ofNullable(
               getRequestObject(request)
                   .flatMap(
                       copyToFilesRequest ->
                           this.getAttachmentToCopy(copyToFilesRequest, zsc)
                               .flatMap(
                                   attachment ->
                                       Try.withResources(attachment::getInputStream)
                                           .of(
                                               inputStream ->
                                                   For(
                                                           Try.of(
                                                               () ->
                                                                   ZimbraCookie
                                                                           .COOKIE_ZM_AUTH_TOKEN
                                                                       + "="
                                                                       + zsc.getAuthToken()
                                                                           .getEncoded()),
                                                           this.getDestinationFolderId(
                                                               copyToFilesRequest),
                                                           this.getAttachmentName(attachment),
                                                           this.getAttachmentContentType(
                                                               attachment),
                                                           Try.withResources(
                                                                   attachment::getInputStream)
                                                               .of(this::getAttachmentSize)
                                                               .flatMap(identity()))
                                                       .yield(
                                                           (cookie,
                                                               folderId,
                                                               fileName,
                                                               contentType,
                                                               fileSize) ->
                                                               filesClient.uploadFile(
                                                                   cookie,
                                                                   folderId,
                                                                   fileName,
                                                                   contentType,
                                                                   inputStream,
                                                                   fileSize)))))
                   .flatMap(identity())
                   .flatMap(identity())
                   .mapFailure(
                       Case(
                           $(ex -> !(ex instanceof ServiceException)),
                           ServiceException::INTERNAL_ERROR))
                   .get())
           .orElseThrow(() -> ServiceException.FAILURE("got null response from Files server."));

@keshav I can do the following (introducing a method to get the cookie):

Optional.ofNullable(
                getRequestObject(request)
                    .flatMap(
                        copyToFilesRequest ->
                            this.getAttachmentToCopy(copyToFilesRequest, zsc)
                                .flatMap(
                                    attachment ->
                                        Try.withResources(attachment::getInputStream)
                                            .of(
                                                inputStream ->
                                                    For(this.getCookie(zsc),
                                                            this.getDestinationFolderId(copyToFilesRequest),
                                                            this.getAttachmentName(attachment),
                                                            this.getAttachmentContentType(attachment),
                                                            Try.of(() -> inputStream),
                                                            Try.withResources(
                                                                    attachment::getInputStream)
                                                                .of(this::getAttachmentSize)
                                                                .flatMap(identity()))
                                                        .yield(filesClient::uploadFile))))
                    .flatMap(identity())
                    .flatMap(identity())
                    .onFailure(ex -> mLog.error(ex.getMessage()))
                    .mapFailure(
                        Case(
                            $(ex -> !(ex instanceof ServiceException)),
                            ServiceException::INTERNAL_ERROR))
                    .get())
                    .orElseThrow(() -> ServiceException.FAILURE("got null response from Files server."));

Edit: there also other ways, I can change a bit some of the private methods. Initially they were different, I can use the try with resources in private methods for example. I understand your point, the handle method should be as clean as possible so to understand the flow.

- Refactor CopyToFiles to make handle method more readable
- Log error instead of debug when API fails
@frisonisland
Copy link
Contributor Author

Made changes about flow and added Log with ERROR when API fails. Check again

@frisonisland frisonisland requested a review from keshavbhatt June 22, 2023 08:31
@Polpetta
Copy link
Contributor

The problem is not the functional approach, is that there are a lot of objects to unwrap. This is kinda a bad design for whom implemented it in the past, little we can do with such a codebase other than trying our best.
The getCookie helps a little I agree

@Polpetta
Copy link
Contributor

Also I saw that you edited stuff related to certbot in ProxtConfGen, like deleteDomainCertbotConfiguration. Why?

@frisonisland frisonisland requested a review from a team as a code owner June 26, 2023 10:11
@frisonisland frisonisland requested review from aheeva-yuliya and removed request for a team June 26, 2023 10:11
@frisonisland
Copy link
Contributor Author

Also I saw that you edited stuff related to certbot in ProxtConfGen, like deleteDomainCertbotConfiguration. Why?

Check again, there are no changes on ProxyConfGen!

- Use Guava not(instanceOf) instead of !( -> instanceOf)
Copy link
Contributor

@Polpetta Polpetta left a comment

Choose a reason for hiding this comment

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

LGTM

@frisonisland frisonisland merged commit 052d460 into devel Jun 26, 2023
@frisonisland frisonisland deleted the feat/CO-727 branch June 26, 2023 12:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants