-
Notifications
You must be signed in to change notification settings - Fork 1
Changing extension to use React #6
base: master
Are you sure you want to change the base?
Conversation
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.
Cool. In general looks good - I've barely done a codelab for React so I haven't reviewed that part too carefully.
src/widgets/concept-widget.tsx
Outdated
<thead> | ||
<tr> | ||
<th>ID</th> | ||
<th style={{textAlign: 'left'}}>Name</th> |
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.
CSS file is definitely preferred to this duplicative inline styling.
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.
Done.
src/widgets/concept-widget.tsx
Outdated
</thead> | ||
|
||
<tbody> | ||
{this.model.concepts.map(function(concept) { |
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.
Should use an ES6 arrow function here as well
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.
Done.
src/widgets/concept-widget.tsx
Outdated
{this.model.concepts.map(function(concept) { | ||
return ( | ||
<tr key={concept.conceptId} onClick={() => | ||
selectRow(concept)} |
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: I'd remove that newline, this is breaking up a short callback function
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.
Done.
src/widgets/concept-widget.tsx
Outdated
</tbody> | ||
</table> | ||
<input value='Generate code' type='button' onClick={generateCode} | ||
disabled={selectedConceptId == null}/> |
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.
!selectedConceptId
might be more readable and catch a few more edge cases: 0, null, undefined
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.
Done.
src/index.ts
Outdated
@@ -13,10 +13,11 @@ import { | |||
|
|||
import {AllOfUsConfig} from './config'; | |||
import {ConceptsService} from './services/concepts.service'; | |||
import {ConceptsWidget} from './widgets/concepts-widget'; | |||
// import {ConceptsWidget} from './widgets/concepts-widget'; |
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.
revert? Or remove widget?
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.
Removed.
src/widgets/concept-widget.tsx
Outdated
disabled={!this.model.initialized}/> | ||
<br/> | ||
<input value='Search' type='button' onClick={searchConcepts} | ||
disabled={!this.model.initialized || this.model.query == null |
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: !this.model.query
would catch the 2nd and 3rd case
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.
Done.
src/widgets/concept-widget.tsx
Outdated
export class ConceptsWidgetModel extends VDomModel { | ||
|
||
public initialized: boolean; | ||
public conceptsLoaded: boolean; |
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.
conceptsLoaded and conceptsLoading seem like they should be mutually exclusive and therefore encoding redundant information. Do you need both? If so, maybe rename or doc
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.
Added an enum that handles the three states here (UNSET, LOADING, LOADED.)
src/widgets/concept-widget.tsx
Outdated
} | ||
|
||
} | ||
|
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: excess newlines
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.
Done.
src/widgets/concept-widget.tsx
Outdated
return null; | ||
} | ||
const img_src = 'https://vignette.wikia.nocookie.net/epicrapbattlesofhistory/' | ||
+ 'images/c/c2/Peanut-butter-jelly-time.gif/revision/latest?cb=20141129150614'; |
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 fun, but should we look for something more consistent with AoU styling?
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.
Yes... I don't have such an asset right now. I asked on Slack where the spinner for workbench comes from... I don't see it in our codebase?
Anyway, for now I'm going to leave this in there to remind us to replace it with something. :)
(In general, this UI is a prototype showing what we can do with JupyterLab extensions and React... I don't intend to actually put it in front of researchers in this form.)
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.
OK, I wound up swapping this out for a React CSS-based spinner from loaders.css.
Would prefer someone with React experience approves. If we don't have anyone available I will dig into the docs later today and join you in the learning process before re-reviewing. |
I turned on "watch" mode for JupyterLab when running locally... now when you make CSS changes you just have to reload the page to pick them up, and when you make TypeScript changes you just have to run the "build" target instead of "install" (the latter takes much longer.) |
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, mostly questions.
LOADED | ||
} | ||
|
||
export class ConceptsWidgetModel extends VDomModel { |
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 some of the docs indicating that you should be emitting a change event like this when you update one of these properties:
https://github.com/jupyterlab/jupyterlab/blob/master/packages/launcher/src/index.tsx#L201
If you dispatched events there, it seems like that would avoid a bunch of the explicit update()
calls you're doing below. Otherwise I'm not sure there's any real reason for you to be using a Model at all here - do you get something else out of it?
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 have a similar question, what does VDomModel
get you? Typical usage is to extend React.Component
}); | ||
}; | ||
return ( | ||
<div> |
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.
So if I understand correctly, we're rerendering the whole react DOM everytime we get an update; so we're just using this as a templating language (without the data binding). I think that's probably fine given our performance needs - just wanted to confirm this was the intent.
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 less OK with this approach. One of the strengths of React's virtual DOM is to allow React to manage the re-rendering. If some element of state changes inside of a component, then React will know to call render for the specific elements that are touched by that element of state. The approach here subsumes that primary React function and takes control over the whole dom.
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 revising my critique here. After reviewing this with Mohs, I think I'm wrong - we still are letting React do the rendering (which it will optimize via the virtual DOM) even if we tell it to re-render the whole table.
<input value='Search' type='button' onClick={searchConcepts} | ||
disabled={!this.model.initialized || !this.model.query}/> | ||
<br/> | ||
{this.loadingImage()} |
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.
Note: some other approaches to composition are listed here: https://medium.com/dailyjs/techniques-for-decomposing-react-components-e8a1081ef5da
But the approach you have now definitely makes sense if we're not really doing data binding here and using the React rerendering stuff.
}); | ||
} | ||
|
||
protected render(): React.ReactElement<any> { |
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 couldn't find documentation for this type. Why any
here - what is that generic value supposed to represent?
I want to spend some time with this PR, but was having trouble getting it up and running. Will touch base with Dan on Monday to get some help with that. Also, we have several experienced react developers we could pull in. @dmohs might have some bandwidth. |
@rushtong I am OOO next week, so it may have to wait until I get back. That said: all you should have to do (hopefully) to get this working is run ./project.rb dev-up, and go to http://localhost:8007 in your browser; once you do, when you open a notebook it should open our extension. If that's not happening, please let me know, and I'll try to reproduce the issue. |
Thanks @danqrodney. With a fresh checkout, that command gives me:
I'm wondering if this project needs to be located somewhere relative to |
Ah ... found it. I needed to run |
); | ||
} | ||
|
||
protected conceptsTable(): React.ReactElement<any> { |
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 would suggest this be a class component on its own, e.g.ConceptTable
. Further, I would break out the rows into another class as well. Right now, we're not doing much with the individual row content aside from loading the concept id, but one could imagine adding sorting and filtering at the table level.
If there is an error accessing the configured workspace (404 in the javascript console), there is an infinite spinner. Should have some kind of error display there. |
When the widget loads and a search table is displayed, the widget window can be too small for the results and hide the "generate code" button. I think we need a scrollbar for that case. |
Thanks for the comments @rushtong and @calbach. The VDomModel / VDomRenderer stuff is JupyterLab-specific... it's wiring up React Elements with Phosphor widgets (which are what gets added to the JupyterLab UI). The only documentation I can find about React in JupyterLab is here: http://jupyterlab.readthedocs.io/en/stable/developer/virtualdom.html I don't know how to make a React component (rather than just an element) live inside a Phosphor widget in JupyterLab. If you know how, and think it would work better, please let me know. Though after my experience trying to get Angular working, I'm wary of any approach which isn't explicitly supported by the JupyterLab framework. In the meantime, I'm going to hit pause on this PR -- building this extension was mostly a proof of concept that we could build something that hits our API and does something useful from JupyterLab, with a technology we like. We may well want an extension like this, but it may be significantly different functionally from what's here now -- we're still working out the plan. (And JupyterLab isn't going to be available in Leo in prod until late next week.) So I will start working on other items while we work out the details. |
No description provided.