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
Selectors everywhere #2507
Selectors everywhere #2507
Conversation
test("handles a focus to next cell keypress on a sticky cell", () => { | ||
|
||
// TODO: This test was silently broken. It was loudly found during a refact. | ||
test.skip("handles a focus to next cell keypress on a sticky cell", () => { |
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.
Yeah, that's fine for now. We probably want to tear out the sticky cells at this point.
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.
Not gonna tear them out in this PR, but I'm for doing it if we think that folks will not be terribly bothered.
Speaking of knowing if folks are terribly bothered, do we have any plans on trying to get anonymized usage data from folks? It'd be cool if we could see how many people are actually sticking cells. That's a whole can of worms, but I'm curious if it's been discussed?
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! We ran an informal poll on twitter in October of 2017.
Do you use "pin cell" in nteract desktop?
# | % | Choice |
---|---|---|
5 | 26.3% | Never, it's so not for me |
5 | 26.3% | Once for funsies |
7 | 36.8% | Hardly ever |
2 | 10.5% | All the time, it's my jam |
19 respondents
I'm willing to say, based on those results, that it would be ok to remove it in favor of maintainability. We can always keep the component + styling in core, just rip it out of the standalone notebook app.
|
||
describe("codeMirrorMode", () => { | ||
test("determines the right mode from the notebook metadata", () => { | ||
// TODO: better way to get dummy state? |
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.
On some level, I think it's ok to only use the state we're actually using for the selectors.
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.
Yah, the one thing I got hung up on was that it's common for use to use fromJS
in tests, but that causes selectors to fail if they expect a Record
and use .
(dot) accessors. I changed all the selectors to use get
and getIn
, which is a pretty easy compromise, but It is a little odd. I guess records all have defaults though, so there's no reason you couldn't instantiate one in a test with only the bits you need filled in?
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.
Ahhhhhh yeah. I suppose the records make it easier, which means using makeAppRecord
and makeDocumentRecord
should make building a dummy simpler than having to change dummy on the fly (or rely on its values).
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.
This is so fantastic! I made a lot of comments that I think are more for future reference, so I don't intend to block this. I'll go back and note which comments we should address first (and as part of this PR).
filename: state.document.get("filename"), | ||
notebook: state.document.get("notebook") | ||
cellFocused: selectors.currentFocusedCellId(state), | ||
cellMap: selectors.currentCellMap(state), |
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.
Refresh my memory, why does the notebook-menu need the whole cellMap
and cellOrder
?
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.
It needs those because it needs to do runAllCells
and such:
BUT, if (when) we have an epic that does this for us... we can rip these out.
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.
ooooooook
I'll get that into an epic since that would help both menu implementations
@@ -80,6 +80,12 @@ export function saveContentEpic( | |||
const state = store.getState(); | |||
const currentNotebook = selectors.currentNotebook(state); | |||
|
|||
// TODO: this will likely make more sense when this becomes less | |||
// notebook-centric. | |||
if (!currentNotebook) { |
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.
We could / probably should emit an error rather than the empty.
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.
Saving I definitely want to make sure we're propagating enough information back up, both for trust and transparency for users, and debuggability for us.
packages/core/src/epics/contents.js
Outdated
// TODO: this will likely make more sense when this becomes less | ||
// notebook-centric. | ||
if (!currentNotebook) { | ||
return of(empty()); |
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.
Aren't you able to return the empty()
observable as is?
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.
😬 ah, yah, you're right, this is returning an observable of an observable, so mergeMap
will only shell off the outer one, leaving empty()
instead of just er... nothing. thanks!
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 i double-checked this (for sanity) and yes, just empty()
should be returned.
if (!cellMap) { | ||
return empty(); | ||
} | ||
|
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.
Since we're only using cell type and cell source here, I think the selector used above could be:
const source = cellSourceById(store.getState(), { id });
const type = cellTypeById(store.getState(), { id });
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.
Updating the above comment to match the use of our cell ids (which of course are in fact refs).
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.
Gonna do this after, but 💯
@@ -21,14 +22,14 @@ function mapStateToProps( | |||
state: Object, | |||
ownProps: CodeMirrorEditorProps | |||
): Object { | |||
const kernel = selectors.currentKernel(state); | |||
const { cursorBlinkRate } = selectors.userPreferences(state); |
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 old silly one configurable bit for codemirror here. 😝
packages/core/src/selectors/index.js
Outdated
if (notebook) { | ||
return notebook.getIn(["metadata", "gist_id"]); | ||
} | ||
return ""; |
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.
There's disparity here in that if ["metadata", "gist_id"]
is not in the notebook, this will return undefined. If the notebook wasn't defined it returns ""
. I'd aim to return null in this one as:
notebook.getIn(["metadata", "gist_id"], null)
return toJS(notebook); | ||
} | ||
return 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.
I see what you mean by if we should throw here... If it's not set we should fail and know where it occurred. Do people normally allow for throwing in exceptions?
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 not sure! I think flow
should protect us in this case when we try to do anything with the selector, so i guess I'd rather return something and then have flow
remind us that it could be null
? I mostly say this because the error would likely originate somewhere in reselect
code, which feels like it might be odd to 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.
I think returning null
and handling it elsewhere is perfect, unless there's some sensible default (like with codemirror mode, etc.)
if (notebookJS) { | ||
return stringifyNotebook(notebookJS); | ||
} | ||
return ""; |
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.
$PATH
an empty notebook. I think I'm a fan of throwing here (or in stringifyNotebook
). Perhaps the right choice though is to handle it in the epic -- if it returns null
for instance, we should take a different path. I guess maybe that's the right choice though -- we do the handling in the epic where we have an ability to inform the UI and the user.
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. I think the moral here is to return null
in these cases. I don't think I want to throw in the selectors...
export const transientCellMap = createSelector( | ||
(state: AppState) => | ||
state.document.getIn(["transient", "cellMap"], Immutable.Map()), | ||
identity |
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.
This seems like one we'll want to do as a byRef
setup 🔜, since no component ever needs all the cells.
return notebook.get("cellMap"); | ||
} | ||
return 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.
I look forward to this being replaced by a selector which is cellByRef
ad891cc
to
00f2705
Compare
packages/core/src/epics/contents.js
Outdated
// TODO: this will likely make more sense when this becomes less | ||
// notebook-centric. | ||
if (!currentNotebook) { | ||
return of(saveFailed()); |
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 do wish we provided more context here, so we know that it's because of a lack of notebook
.
00f2705
to
c15c5f9
Compare
metadata.getIn(["language_info", "codemirror_mode"]) || | ||
metadata.getIn(["kernel_info", "language"]) || | ||
metadata.getIn(["kernelspec", "language"]) || | ||
CODE_MIRROR_MODE_DEFAULT |
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.
Fantastic!
Howdy! I'm 🔓🤖! In order to keep information timely (based on the most recent release), we want all activity to be added to either new issues or open issues and PRs. In service to that goal, I, the lock bot close inactive closed issues when they haven't had activity in 120 days. Feel free to open a new issue for related bugs and link to relevant comments from this thread. |
In response to some comments @rgbkrk left in #2500 I used selectors in
mapStateToProps
. I also couldn't help myself and added a commit in here that updates some usage ofmapDispatchToProps
. I think the latter change is an overall improvement. However, I don't know how cleverly/simply themapDispatchToProps
arg is handled inconnect
. It could potentially be a perf issue to re-create all the functions inmapDispatchToProps
... just wanted to draw that out.Also, I think I found a test that was yielding false positives. You'll see it skipped in the diff. Here's an issue I opened about it:
#2506
Just to reiterate, the goal of these refactors are to ensure that our code doesn't depend on the structure of our state 🌲. We want to reorganize the state and add useful things like app
ref
s to our state (see theplan.md
file for details on that).