-
Notifications
You must be signed in to change notification settings - Fork 530
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 1473330 - Translate.Next architecture #989
Conversation
Thanks for the ping @adngdb — I'm currently busy with other stuff and won't be able to provide feedback immediately but would love to check the details and comment on them as I find some spare time. |
Thanks for sharing this. A couple of questions, mostly around onboarding impact and developer productivity: One is about the use of flowscript. I see some value in typing, but does it outweight the cost? My untrained eye sees a lot of sigils and syntax that's flow-specific, and that anybody wanting to contribute to pontoon frontend would need to understand. I confess that there's a couple of places where I just don't know what the code is doing, https://github.com/adngdb/pontoon/blob/translate.next/frontend/src/modules/entitieslist/actions.js is one example. There's so much flow in there that I don't find the js with untrained eyes. There's also quite a bit of set-up to do for https://flow.org/en/docs/editors/. I guess that flowscript without editor support is actually hurting more than it helps? 'Cause you'll be in syntax error land in many places? For eslint, we'd need How much of flow do we get if we'd not use explicit type annotations? Another question I have is about https://github.com/adngdb/pontoon/blob/translate.next/frontend/README.md#enable-websocket-and-warm-reloading: You mention a work-around for Firefox. Does that mean that it doesn't work in Chrome or Edge? Would there be ways around this eventually? Like django channels or so? Also, do we need websockets just in development, or also in production? Last question is about using yarn as test runner. Do you have plans to integrate that back into the django test runners, or would we be left with two entry points to testing? |
Hey, I'll need a few moment (days) to dig into details but this PR gives me positive feelings, because of effort you put into documentation and communication (not in the code itself but in other places). The static typing and test coverage in the front-end are steps in the good direction and I eagerly look into the future. |
I agree with that, it does add a lot of code and makes things a lot more complex. I do however believe it is in the end not so different to writing C++ or Rust, and we'll get used to it. However, if we can take the time to learn Flow, that won't be the case for every contributor, so that argument is a good one. Embracing Flow has a lot of benefits for big applications, it takes care of detecting a lot of bugs before you can even run into them in production. The addons team is using it for the new addons website, and @kumar303 recommended using it. I see 2 questions thus: do we value code safety over code complexity? Is our application going to be big enough to justify using Flow? @kumar303, did your team think about code complexity and contributions? If so, what was your conclusion?
Ah, there's actually a command to run flow in a terminal and have it revalidate for each change. I personally haven't integrated it to my editor, I think that would be up to developers and their preferences. The good thing, at least in Atom, is that it doesn't show any syntax errors with Flow. I think the linting plugins I use do support Flow out of the box.
Dunno, but given the name, I guess we will. :P
Close to none, if I understand it correctly. Flow can infer types in some cases, but that's quite limited.
Yes, it does mean it won't work in Chrome, I don't know about Edge. I know of no way around that in Chrome, the feature simply doesn't exist there. We can probably work around it in django, but that would be quite complex (at least according to the blog post I used to set this up). If you can't turn on websockets, you will see errors in the console (that's not very impacting) and you'll have to reload your django server regularly, because polling requests don't close, and after so many web page reloads, the django process won't be able to accept new requests. It's nothing blocking, but it's painful. Websocket is only useful in development, by react hot reloader to, well, reload the page when there are changes to the files.
Good question! I've never thought about doing that. Right now we actually have 3 different test suites: django, tests for Ryan's JS code, and tests for Translate.Next. I don't think we can run JS tests with |
I also wonder about the need to restart the server when switching waffle flags, both Adrian and I would prefer that to "just work". Adrian said he'd look, if I left a comment. |
Yes, after using it across our team (and contributors) for quite a while now, I still think static typing is very valuable for us. Here are some things that we benefit from on AMO (your mileage may vary):
Some additional things I like personally:
Yes, definitely. Believe it or not, Flow actually helps contributors! This is something I experienced on web-ext which has more contributors than AMO. The problem is that many contributors are not experts in JavaScript so they get caught in many pitfalls. If they use With Flow, the contributor receives specific feedback about what they are doing wrong. Some messages are easier to decipher than others but it is definitely easier than trying to write JavaScript when you are not an expert. Static typing does introduce code complexity. This is something worth considering. I would like to see an improvement to how actions/reducers are typed, maybe like this idea (I can't get it to work, heh). One nice thing, though, is you can introduce types gradually. This might let you focus on important parts of the app first, such as the part that interacts with the API. Flow still has bugs in it and they can eat a lot of your time. It's better than it was, though. You can use |
After investigating a bit, I suspect it is the nature of |
I am done with the remaining of the work I wanted to do here. Review away, folks! 🤘 |
Note for reviewers: If you try to run this branch, you'll see
As @adngdb suggested, run these commands (outside docker) before running
Also note that static files currently don't load unless you turn on the feature:
|
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 far from done with the review, so consider this an interim report of the most significant developments that I've encountered with this PR so far:
I got the new laptop!
Later on I set up Pontoon on it via Docker and submitted a patch!
I'm now running this branch and I can confirm that /translate
loads some strings.
Kindly allow me to take some more time and dig deeper into this whole new World for me.
Enough is enough, so I'll stop here and continue with the review.
@@ -0,0 +1,151 @@ | |||
# Translate.Next — a better Translate app for Pontoon |
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 like that you put code documentation in here!
For the sake of consistency, could you please also (as part of this PR, otherwise we'll forget) migrate existing code documentation from RTD?
http://mozilla-pontoon.readthedocs.io/en/latest/dev/sync.html -> /pontoon/api/README.md
http://mozilla-pontoon.readthedocs.io/en/latest/dev/api.html -> /pontoon/api/README.md
frontend/README.md
Outdated
<table> | ||
<tr> | ||
<td>Bootstrapper</td> | ||
<td>[create-react-app](https://github.com/facebook/create-react-app)</td> |
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.
These links don't seem to render:
https://github.com/adngdb/pontoon/blob/translate.next/frontend/README.md
frontend/README.md
Outdated
|
||
### Development | ||
|
||
If you're using docker, `make run` automatically starts both a webpack server (on port 3000) and a django server (on port 8000). django is the server you want to hit, and it will then proxy appropriate requests to the webpack server. |
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.
Capital D in django.
- [The Anatomy Of A React & Redux Module (Applying The Three Rules) — Jack Hsu](https://jaysoo.ca/2016/02/28/applying-code-organization-rules-to-concrete-redux-code/) | ||
- [Additional Guidelines For (Redux) Project Structure — Jack Hsu](https://jaysoo.ca/2016/12/12/additional-guidelines-for-project-structure/) | ||
|
||
### Tools documentation |
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 already covered on top of the page. Remove?
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.
These point directly to the documentation of the tools, I think it's not a problem to have them kind of doubled because I expect it will be convenient at least to me in the future. :)
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 think this is going to be great. Actually, it already is great!
The docs, comments and commit messages helped me a lot in understanding how the code works and also dig into technologies I haven't used before (still have some work to do here). Thank you!
My only question is this: now we have "React code" in /frontend and /static folders (tags). Should we merge?
Other than that, I think you still need to fix the setup and then we're good to go: #989 (comment).
pontoon/translate/urls.py
Outdated
# Note that because we override the static files serving, you will need to | ||
# run your local django server with the `--nostatic` option. That's | ||
# automatically done when running with `make run`. | ||
if settings.DEBUG: |
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.
DEV
?
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 a bit confused about that. On Socorro I only ever used DEBUG
. What's the difference between the two?
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 see that we have DEV
and DEBUG
both set to False
in prod and stage, and both set to True
in local development settings. So it might be a good idea to join them.
|
||
export default function reducer( | ||
state: State = initialParams, | ||
action:Action, |
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.
Nit: add space.
pontoon/translate/views.py
Outdated
""" | ||
# Redirect websocket requests directly to the webpack server. | ||
if request.META.get('HTTP_UPGRADE', '').lower() == 'websocket': | ||
print UPSTREAM + request.path |
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.
Remove debug message?
with override_switch('translate_next', active=True): | ||
response = client.get(url) | ||
assert response.status_code == 200 | ||
print response.content |
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.
Remove debug message?
This has been set up using create-react-app.
This provides a working prod and dev environment. In production, django will serve the index.html file as a template, and files built by webpack will be collected and distributed with other django static files. In development, all requests are proxied to the dev webpack server, allowing all dev niceties to be used.
All features should be self-contained into a module in src/modules/. All code that is shared amongst several modules should go into a module in src/core/.
The technology stacks are quite different, merging them might (or might not) be difficult. I'd rather not do that for now and focus on Translate.Next. |
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.
Wonderful! Thanks for spearheading this effort; it looks great! And thanks for writing the docs, they helped me review this PR a lot!
I haven't used Flow in production yet, but I'm looking forward to learning more about it. I'm convinced that any type checking technology will benefit Pontoon in the long run.
I like the directory layout proposed for frontend/
. One suggestion I might make is to use the .mjs
extension for all new code. I expect that it will become the extension for all JS in the future; using it now will spare us the trouble of The Great Rename later on.
I'm yet to try this PR locally because I have run into some trouble with Docker on Windows. I'll give it another try tomorrow. I also haven't looked at the Python nor the CSS parts of this PR.
.eslintignore
Outdated
|
||
# This file is the default one that we got from create-react-app. | ||
# We are not using it yet, no need to check it. | ||
frontend/src/registerServiceWorker.js |
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.
Maybe just remove it for now, if it's not used anyways?
docker/Dockerfile
Outdated
RUN ln -s /webapp-frontend-deps/node_modules /app/node_modules | ||
RUN ln -s /webapp-frontend-deps/assets /app/assets | ||
RUN ln -s /webapp-frontend-deps/frontend/node_modules /app/frontend/node_modules | ||
RUN ln -s /webapp-frontend-deps/frontend/build /app/frontend/build |
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.
AFAIK, each RUN
creates a new layer in the image. I think the good practice is to use &&
to execute more than one command when they are related to each other.
|
||
### Modules | ||
|
||
Each module should be in a folder and have an `index.js` file that serves as the module's public interface. Outside of the module, always import from the module's index, and never from specific files. This allows us to easily evolve modules and keep things decoupled, which reduces code complexity. |
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.
👍
frontend/src/App.js
Outdated
* Main entry point to the application. Will render the structure of the page. | ||
*/ | ||
class App extends Component { | ||
componentWillMount() { |
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.
componentWillMount
will be deprecated in a future React 16.x version and removed in React 17. See https://reactjs.org/blog/2018/03/29/react-v-16-3.html#component-lifecycle-changes. It's best to stick to componentDidMount
for side-effects. The reason, in short, is that with async rendering and React Fiber, components may start mounting more than once if they are interrupted by a higher-priority task.
export default { | ||
// actions, | ||
constants, | ||
reducer, |
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.
Why are these exported as default
rather than as named exports?
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 you give a code example?
With this I can import the module by writing import navigation from 'core/navigation';
and then use navigation.reducer
for example. If we exposed everything as named export, we would have to do import * as navigation from 'core/navigation';
or something, right?
I'm honestly not sure what's best. I went with this because that was the syntax being used in the reference I read: https://jaysoo.ca/2016/02/28/applying-code-organization-rules-to-concrete-redux-code/
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'd recommend the following export structure:
export {default as reducer} from "./reducer";
export const NAME = "...";
// Or, if they're stored in constants.js:
export * from "./constants";
Constants are already uppercase and shouldn't conflict with other exports from index
. Flatter hierarchies are quicker to work with :)
Using this elsewhere would look like the following:
// Same as you wrote:
import * as navigation from "core/navigation";
// Or using a rename:
import {reducer as navigationReducer} from "core/navigation";
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.
Looks good to me. Done!
|
||
fetch(url, requestParams) | ||
.then((response: Object) => response.json()) | ||
.then((content: Object) => dispatch(receive(content.entities))); |
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 we start using async
/await
?
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.
frontend/src/rootReducer.js
Outdated
[navigation.constants.NAME]: navigation.reducer, | ||
}); | ||
|
||
export default rootReducer; |
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.
Nit: you don't have to bind the combined reducere to const rootReducer
if you only export default
it right after. export default combineReducers(...)
will work too. (This applies to store.js
as well.)
frontend/README.md
Outdated
- `index.js` — public interface of the module | ||
- `actions.js` — actions that can be used to fetch data from an API, trigger changes, etc. | ||
- `reducer.js` — a single Redux reducer (if you need to have more than one reducer, you probably actually need to make several modules) | ||
- `constants.js` — a list of constants required by the module or other modules. It is recommended to expose a `NAME` constant here, that will contain the key to use to expose the reducer in the global store. |
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.
Perhaps change the wording to make this a bit more clear:
... It's recommended to define a
NAME
constant here which should be a unique identifier of the module. This name will be used incombineReducers
and in allmapStateToProps
to identify the subtree of the global state relevant to this module.
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, having a separate file when there's only one constant might be a bit of an overkill. There two modules in this PS and both only have NAME
defined. I'd put it directly in index.js
and then figure out the good practice for defining more once they are actually needed.
frontend/public/manifest.json
Outdated
"name": "Create React App Sample", | ||
"icons": [ | ||
{ | ||
"src": "favicon.ico", |
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.
favicon.ico
is missing from the this PR.
import * as constants from './constants'; | ||
import reducer from './reducer'; | ||
|
||
export type { Action, State } from './reducer'; |
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.
What is the purpose of exposing Action
and State
as part of the public interface of the module?
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.
Good catch, that's some leftover from when I was struggling with Flow.
Many comments. Wow better!
@stasm I got started on renaming everything to |
Tant pis. I don't think it's crucial to use |
Now I get this error after
|
@mathjazz can you please |
That folder doesn't exist. I also noticed this after
|
No, that happens on master, too (which works apart from that). |
@mathjazz that last commit should fix your issue. |
Es funktioniert! |
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 everyone for the reviews and comments! For those who did not have a chance to do it yet, please do feel free to do it anytime, even in this closed PR. I can address your comments in follow-up pull requests, and it's important for me that we do this correctly, so any additional pair of eyes looking at it is welcome. |
Here it is at last! The first concrete step towards a rewritten Translate app.
I am aiming at making this first PR a learning experience for everyone who's going to be involved in this. It thus has a very basic feature (fetching entities from django and showing them), but it has the architecture that I think makes sense for a big project, it has tests, it has type checking, and it has documentation.
For this review, I would thus ask that you read the documentation and comment on it (it should serve as a reference for future contributors, hence I want it to be really good), investigate my technical choices, and investigate my architectural choices. Those are the main things I'd like you all to focus on, but of course, the rest is also important.
I thus nominate for review: @mathjazz, @stasm and @Pike. @jotes, @julen, @MikkCZ, and really anyone else interested, I would love to have your feedback, or full review, as well, if you can find the time.
Note that the new feature is hidden behind a switch, so there should be no risk merging it, as it won't be accessible in stage or prod unless we explicitly turn it on.
Things that are missing and might be added to this PR:
Localization with Fluent(will be added with the first feature that has actual text content)CSS extension (SASS? )(no need for now, will revisit later)