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

Fix Bug 1603610 - Enable users to leave comments on translations #1524

Merged
merged 58 commits into from Jan 24, 2020

Conversation

abowler2
Copy link
Collaborator

@abowler2 abowler2 commented Dec 18, 2019

Initial work to add the ability to leave comments on translations.


At the moment I'm just printing the comments into the history panel. Moving forward from here I believe I will need to add a Comment component in order to render and style all the information properly. If that is indeed the case should I add that component to the History module or the Translation module?

Also, I'm not sure if a Prefetch is needed for retrieving the comments in map_entities within models.py, but I was unsuccessful in my attempts at that. Would appreciate your thoughts and insights on this.

TODO:

  • File a bug to rename Translation.user to Translation.author.
  • File a bug to make the Add comment fields grow as you type.
  • ./manage.py makemigrations says we need to make some migrations.
  • Use UserAvatar component wherever possible.

@mathjazz mathjazz changed the title [WIP] Fix Bug 1603610 - Enable reviewers to leave comments on translations [WIP] Fix Bug 1603610 - Enable users to leave comments on translations Dec 18, 2019
Copy link
Collaborator

@mathjazz mathjazz left a comment

Choose a reason for hiding this comment

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

Awesome work, @abowler2!

I think you'll need a Comments component in the Translation component. It should render two other components:

  • Comment for the display of the comment
  • AddComment for adding the comment

I believe you'll indeed need to prefetch comments in get_translation_history() before you serialize them. I can help with that if you run into issues.

frontend/src/modules/history/components/Translation.js Outdated Show resolved Hide resolved
frontend/src/core/api/types.js Outdated Show resolved Hide resolved
frontend/src/core/api/types.js Outdated Show resolved Hide resolved
frontend/src/core/api/types.js Outdated Show resolved Hide resolved
pontoon/base/models.py Outdated Show resolved Hide resolved
pontoon/base/models.py Outdated Show resolved Hide resolved
pontoon/base/models.py Outdated Show resolved Hide resolved
frontend/src/modules/history/components/Translation.js Outdated Show resolved Hide resolved
pontoon/base/admin.py Outdated Show resolved Hide resolved
@abowler2
Copy link
Collaborator Author

Current progress for review. There is obviously still a lot to be done but wanted to check in to make sure all is on track (Note: I used some temporary filler values for titles and inline styling in the components that is not intended to stay 😄 )

In addition to the obvious need for styling, I still have to add the Prefetch in get_translation_history, the Delete and Share buttons in the Comment component, and build out the AddComment component.

Please let me know what changes are needed at this point.

Copy link
Collaborator

@mathjazz mathjazz left a comment

Choose a reason for hiding this comment

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

Great progress!

We should move all three Comment* components from core/translation to the newly created core/comments. Looking at the spec, we should benefit from reusing these components for team comments.

pontoon/base/models.py Outdated Show resolved Hide resolved
frontend/src/core/translation/components/Comment.js Outdated Show resolved Hide resolved
frontend/src/core/translation/components/Comment.js Outdated Show resolved Hide resolved
frontend/src/core/translation/components/Comment.js Outdated Show resolved Hide resolved
frontend/src/core/translation/components/Comments.js Outdated Show resolved Hide resolved
frontend/src/core/translation/components/Comment.js Outdated Show resolved Hide resolved
frontend/src/core/translation/components/Comment.js Outdated Show resolved Hide resolved
pontoon/base/views.py Outdated Show resolved Hide resolved
pontoon/base/models.py Show resolved Hide resolved
frontend/src/core/translation/components/Comment.js Outdated Show resolved Hide resolved
Copy link
Collaborator

@adngdb adngdb left a comment

Choose a reason for hiding this comment

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

This is a great start April! I've made a bunch of comments about good practice, conventions and stuff, but overall the code looks good.

In terms of workflow, maybe it won't be needed in this case (this PR is still of manageable size) but I encourage you to split your work into the smallest possible chunks. For example, you could have one PR for the back-end / models features, using unit tests to make sure it works as expected, and then have a follow-up PR for the front-end work.

Also, please do not forget unit tests! They are your greatest allies to make sure your code works. And even though I'm not good at it, I assure you that test-driven development is great. It forces you to think about what exactly you want to build before coding it, and that's both a great exercise and just good development practice.

pontoon/base/views.py Outdated Show resolved Hide resolved
pontoon/base/views.py Outdated Show resolved Hide resolved
frontend/src/core/api/types.js Outdated Show resolved Hide resolved
frontend/src/core/translation/components/Comment.js Outdated Show resolved Hide resolved
frontend/src/core/translation/components/Comment.js Outdated Show resolved Hide resolved
frontend/src/core/translation/components/Comments.js Outdated Show resolved Hide resolved
frontend/src/core/translation/components/Comments.js Outdated Show resolved Hide resolved
frontend/src/core/translation/index.js Outdated Show resolved Hide resolved
frontend/src/modules/history/components/Translation.js Outdated Show resolved Hide resolved
frontend/src/modules/history/components/Translation.js Outdated Show resolved Hide resolved
@abowler2
Copy link
Collaborator Author

Made the requested changes. Please let me know if any further changes are needed.

RE: the failing Translation test ~ I refactored the user's avatar to use the new UserImage component which causes the test to fail based on only one a tag being present now. Not sure if I should adjust the test to account for the change or if there is something I missed when using the new component.

Copy link
Collaborator

@adngdb adngdb left a comment

Choose a reason for hiding this comment

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

Great follow-up! I've made a few more comments, all little things around conventions or accessibility.

Please do add unit tests for all your code though! 😇

RE: the failing Translation test ~ I refactored the user's avatar to use the new UserImage component which causes the test to fail based on only one a tag being present now. Not sure if I should adjust the test to account for the change or if there is something I missed when using the new component.

You do need to adjust the test. Since you've added a layer of indirection, you do not want to test the content of the component anymore (instead you want to do that in that component's unit tests) but just verify that the component is created. Eventually that it is passed the right props, if that makes sense.

frontend/src/core/comments/components/Comment.js Outdated Show resolved Hide resolved
frontend/src/core/comments/components/Comment.js Outdated Show resolved Hide resolved
frontend/src/core/comments/components/Comment.js Outdated Show resolved Hide resolved
frontend/src/core/comments/components/Comment.js Outdated Show resolved Hide resolved
frontend/src/core/comments/components/CommentsList.js Outdated Show resolved Hide resolved
frontend/src/core/comments/index.js Outdated Show resolved Hide resolved
frontend/src/core/user/components/UserImage.js Outdated Show resolved Hide resolved
frontend/src/core/user/components/UserImage.js Outdated Show resolved Hide resolved
frontend/src/core/user/components/UserImage.js Outdated Show resolved Hide resolved
'translation': self.translation_id
"author": self.author.name_or_email,
"username": self.author.username,
"userGravatarUrlSmall": self.author.gravatar_url(88),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, here's the trick: Python variables should use underscore_case. :) This is confusing, sorry. We effectively have two different languages with different naming conventions. We want to strictly enforce those naming conventions. The only time when they conflict is through the API. And that's where we need some specific code to manually convert those names.

So, here you should revert this change. But keep your type definition in JS. Then on the API function, in front-end, where you get the data, you should have some logic to transform the data, mapping one convention (underscore_case from Python) to the other (camelCase for JS).

Here's where the data gets fetched:

async getHistory(

Side note: if this data was exposed with GraphQL, we wouldn't have that problem, as it handles that conversion automatically for us. Sadly, this is not the case yet, but doing this conversion now means it will be easier later on to switch to fetching that data through GraphQL.

@abowler2
Copy link
Collaborator Author

Sending up the updates from the last review. I'm planning to work on the delete and share button functionality next and then styling the elements.

The one thing I did have issues with was the Copy to Translation title which displays on hover that we discussed last week. It's being displayed from the Localization tag, and I was not able to successfully change it up without breaking it. I figured I would work on it more when I begin the styling, but your thoughts on how I can approach it are always welcome 😄

@@ -0,0 +1,2 @@
{
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please don't send this file to the repo. :) (You can add it in the .gitignore file if you want, or in your .git/info/exclude file so that it's only for your local git folder.)

Copy link
Collaborator

Choose a reason for hiding this comment

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

This one is still around. :)

frontend/src/core/comments/components/Comment.js Outdated Show resolved Hide resolved
frontend/src/core/comments/components/Comment.js Outdated Show resolved Hide resolved
frontend/src/core/user/components/UserAvatar.js Outdated Show resolved Hide resolved
frontend/src/core/api/entity.js Outdated Show resolved Hide resolved
frontend/src/core/comments/components/Comment.test.js Outdated Show resolved Hide resolved
frontend/src/core/comments/components/Comment.test.js Outdated Show resolved Hide resolved
frontend/src/core/comments/components/Comment.test.js Outdated Show resolved Hide resolved
frontend/src/core/comments/components/Comment.test.js Outdated Show resolved Hide resolved
renamedResultsKeys.comments = renamedCommentsKeys;
}

return [renamedResultsKeys];
Copy link
Collaborator

Choose a reason for hiding this comment

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

I find this code too complex, and too specific. Once again, apologies for opening a can of worms here. Let's still get to the end of it, but by using a more generic solution that we can then apply to other places around the API.

I found this article that shows a nice solution to transform code from snake_case to camelCase: https://matthiashager.com/converting-snake-case-to-camel-case-object-keys-with-javascript

I'd take that code and put in the api/base.js file, add a method there to transform objects, and then use that method here to convert the results.

@abowler2
Copy link
Collaborator Author

Updated the camelCase conversion as per @adngdb's note. However, I'm not sure if I 'typed' the argument that is passed in properly. Technically it is being passed an Array<Object> from getHistory, but if I type it as that I get Flow errors within the function. Also, when this is used for other api's it might not be the same. So, I have it just as an Object which does not throw any errors from Flow.

pontoon/base/models.py Outdated Show resolved Hide resolved
const { comment } = props;

if (!comment) {
return null;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: too much indent.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: Still too much indent.

frontend/src/core/comments/components/CommentsList.js Outdated Show resolved Hide resolved
@mathjazz
Copy link
Collaborator

Also, when this is used for other api's it might not be the same. So, I have it just as an Object which does not throw any errors from Flow.

I believe this is one of those rare cases where using Object is actually permissible - for the reason you mentioned.

@abowler2
Copy link
Collaborator Author

Early styling of Comment element for review.

One of the issues that came up with the change in structure is that the bottom border is now above the comments since they have been separated into individual divs. I was thinking this could be set up as a conditional class that would be placed based on the existence of comments or not. Let me know if you think a different approach would be better.

Copy link
Collaborator

@mathjazz mathjazz left a comment

Choose a reason for hiding this comment

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

Nice progress!

You should move the border styling from the .translation element to the li element. Feel free to add className to it if that makes it easier to define the selector reliably.

The avatars don't show up yet and the comment styling also applies to the metadata comments:
Screenshot_2019-12-31 Slovenian (sl) · Firefox

frontend/src/core/comments/components/CommentsList.test.js Outdated Show resolved Hide resolved
frontend/src/core/api/base.js Outdated Show resolved Hide resolved
@@ -0,0 +1,2 @@
{
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This one is still around. :)

const { comment } = props;

if (!comment) {
return null;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: Still too much indent.

frontend/src/core/comments/components/Comment.js Outdated Show resolved Hide resolved
frontend/src/core/comments/components/Comment.css Outdated Show resolved Hide resolved
frontend/public/static/locale/en-US/translate.ftl Outdated Show resolved Hide resolved
frontend/src/core/api/base.js Show resolved Hide resolved
frontend/src/core/api/base.js Outdated Show resolved Hide resolved
frontend/src/core/api/base.js Outdated Show resolved Hide resolved
frontend/src/core/user/components/UserAvatar.js Outdated Show resolved Hide resolved
frontend/src/core/api/base.js Outdated Show resolved Hide resolved
frontend/src/core/api/base.js Outdated Show resolved Hide resolved
@adngdb
Copy link
Collaborator

adngdb commented Dec 31, 2019

The Black formatting commit has been merged to master, causing conflict on this branch. Please rebase and fix the conflicts as soon as you can, to avoid potentially more conflicts later on! And my apologies for disturbing your workflow, but I strongly believe now was the best time to do this. :-)

@abowler2
Copy link
Collaborator Author

The avatars don't show up yet and the comment styling also applies to the metadata comments:

That is really weird ~ Here is what it looks like on my side:

Screen Shot 2019-12-31 at 8 04 56 AM


import type { TranslationComment } from 'core/api';
import type { HistoryTranslation } from 'modules/history'
import type { UserState } from 'core/user';
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: we sort imports by module, i.e. the value following from.

let canReject = (canReview || (ownTranslation && !translation.approved)) && !isReadOnlyEditor;
let canDelete = (isTranslator || ownTranslation) && !isReadOnlyEditor;
let canReject = (isTranslator || (ownTranslation && !translation.approved)) && !isReadOnlyEditor;
let canComment = (isTranslator || ownTranslation);
Copy link
Collaborator

Choose a reason for hiding this comment

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

According to the "Permissions" section of the spec, any logged in contributor canComment, so the value should be user.isAuthenticated. And then you don't need the !! part of the !!canComment below.

I think it's still valuable to have the canComment variable defined along with other CanSomething permissions.

@abowler2
Copy link
Collaborator Author

Sorry, pushed and then realized the translation test was broken. Working on it now

@@ -286,6 +302,7 @@ describe('<TranslationBase>', () => {
translation={ translation }
entity={ DEFAULT_ENTITY }
locale={ DEFAULT_LOCALE }
user={ 'Andy_Dwyer' }
Copy link
Collaborator

Choose a reason for hiding this comment

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

While this probably makes the test pass, this is better: user={ { username: 'Andy_Dwyer' } }.

@mathjazz
Copy link
Collaborator

mathjazz commented Jan 22, 2020

These are the CSS fixes I promised, which:

  1. Make the Comment toggle and Show diff toggle look & feel the same way.
  2. Unify the display of comments and the Add comment form.
  3. Fix a few bugs.

Please apply the patch.

@mathjazz
Copy link
Collaborator

@abowler2 One more patch please - it makes the input fields styled consistently.

diff --git a/frontend/src/core/comments/components/AddComment.css b/frontend/src/core/comments/components/AddComment.css
index a3fd9f5d1..0f0915bfd 100644
--- a/frontend/src/core/comments/components/AddComment.css
+++ b/frontend/src/core/comments/components/AddComment.css
@@ -1,10 +1,10 @@
 .comments-list .add-comment textarea {
-    background-color: transparent;
-    border: solid 1px #5E6475;
+    background: #333941;
+    border: none;
     border-radius: 4px;
     color: #FFFFFF;
     font-size: 11px;
-    height: 22px;
-    line-height: 22px;
+    height: 24px;
+    line-height: 24px;
     padding: 6px;
 }
diff --git a/frontend/src/modules/machinery/components/Machinery.css b/frontend/src/modules/machinery/components/Machinery.css
index b988b51a0..438089d2e 100644
--- a/frontend/src/modules/machinery/components/Machinery.css
+++ b/frontend/src/modules/machinery/components/Machinery.css
@@ -23,7 +23,7 @@
 }
 
 .machinery > .search-wrapper input[type=search] {
-    background: #4D5967;
+    background: #333941;
     border: none;
     border-radius: 20px;
     box-sizing: border-box;
diff --git a/frontend/src/modules/search/components/SearchBox.css b/frontend/src/modules/search/components/SearchBox.css
index 0817c68cb..fa4e2b90f 100644
--- a/frontend/src/modules/search/components/SearchBox.css
+++ b/frontend/src/modules/search/components/SearchBox.css
@@ -4,7 +4,7 @@
 }
 
 .search-box input[type="search"] {
-    background: #4D5967;
+    background: #333941;
     border: none;
     border-radius: 20px;
     box-sizing: border-box;

Copy link
Collaborator

@adngdb adngdb left a comment

Choose a reason for hiding this comment

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

Nearly there! Excellent work @abowler2!

## Allows user to leave comments on translations

comments-AddComment--input =
.placeholder = Write a comment...
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: please use the actual character here: .

[one] { $commentCount } Comment
*[other] { $commentCount } Comments
}
.title = Toggle translation comment
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd do "comment" -> "comments".

id='comment-input'
name='comment'
dir='auto'
placeholder={ `Write a comment ${'\u2026'}` }
Copy link
Collaborator

Choose a reason for hiding this comment

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

No space before the ellipsis, and I believe you can simply use the actual character, no need to use the Unicode representation: (you can just copy and paste it ;) ).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Note that, whenever you change localized content, what actually matters is what is in the en-US FTL file, not what is in the code. That's there only to help developers understand what the content is without having to go look into the .ftl files, but that content will never be shown to users. We should aim to have content in code always in sync with content in the en-US reference FTL files, even if that's a bit painful to do.


const DEFAULT_USER = {
user: 'RSwanson',
username: 'Ron_Swanson',
Copy link
Collaborator

Choose a reason for hiding this comment

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

😍

frontend/src/core/comments/index.js Show resolved Hide resolved
/>
</Localized>
</div>;

Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: please remove this empty line.

/>
className={ commentCount === 0 ? 'toggle-comments' :
'toggle-comments'
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Since the className is now the same regardless of commentCount this should just set the className to toggle-comments without the ternary, correct?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I went ahead and made this change. Just let me know if I'm missing something and the logic needs to be reapplied.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That ternary was weird anyways. :) "If true X else X". :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Originally there were two different classes based on the condition, which made more sense 😄

id={ commentCount === 0 ?
'history-Translation--button-comment' :
'history-Translation--button-comments'
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

It would be more readable if each <button> would be wrapped in their own <Localized> element.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I tried that first but couldn't get it working. I'll have another go to see if I can work it out as I agree it would be much easier to understand what is happening there.

/>
className={ commentCount === 0 ? 'toggle-comments' :
'toggle-comments'
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

That ternary was weird anyways. :) "If true X else X". :)

Copy link
Collaborator

@mathjazz mathjazz left a comment

Choose a reason for hiding this comment

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

Great work, only found two nits!

{ commentCount === 1 ?
`${commentCount} Comment` :
`${commentCount} Comments`
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

You should simplify this to a simple string, e.g.:

{ `${commentCount} Comments` }

See:
#1524 (comment)


if (!translation.uid) {
return <span>{ translation.user }</span>;
renderCommentButton(commentCount: number) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

renderCommentToggle would be consistent with the naming used by the Diff toggle.

if (!translation.uid) {
return <span>{ translation.user }</span>;
renderCommentButton(commentCount: number) {
if(commentCount === 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: Missing space.

return <Localized
id='history-Translation--button-comment'
attrs={{ title: true }}
$commentCount={ commentCount }
Copy link
Collaborator

Choose a reason for hiding this comment

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

We don't use this variable.

>
{ 'Comment' }
</button>
</Localized>
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: remove blank line.

{ commentCount === 1 ?
`1 Comment` :
`${commentCount} Comments`
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

@abowler2 abowler2 changed the title [WIP] Fix Bug 1603610 - Enable users to leave comments on translations Fix Bug 1603610 - Enable users to leave comments on translations Jan 24, 2020
@mathjazz
Copy link
Collaborator

mathjazz commented Jan 24, 2020

Alright, we're done here! Great work, April!

Before we merge, please disable the feature until we land Team Comment by:

    # TODO: Remove as part of bug 1361318
    return JsonResponse(
        {"status": False, "message": "Not Implemented"},
        status=501,
    )

Copy link
Collaborator

@mathjazz mathjazz left a comment

Choose a reason for hiding this comment

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

Excellent job, @abowler2! \o/

@mathjazz mathjazz merged commit 597c496 into mozilla:master Jan 24, 2020
@abowler2 abowler2 deleted the translation-comments branch January 24, 2020 20:03
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.

None yet

3 participants