-
Notifications
You must be signed in to change notification settings - Fork 41
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
Search page tech improvement. Uses Pulse API to get search result. #348
Conversation
@alanmoo since you've done similar things on the Science site, can you review this PR? 🍻 |
@@ -66,15 +88,6 @@ export default React.createClass({ | |||
return entries.sort((a,b) => { | |||
return bookmarkedIdArray.indexOf(a.id.toString()) > bookmarkedIdArray.indexOf(b.id.toString()); |
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.
Is it worth doing this sorting on Pulse API side instead?
e.g, /entries/?ids=3,1,5
will return entry no.3 first, followed by no.1, and lastly no.5.
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.
Is this something you're using in a lot of places? The db returns these in numerical order by default, unfortunately so it's non-trivial to make it behave like this. That's not to say it's incredibly difficult, I'm just not sure it's worth the lift here if there's a JS function that does what you need relatively easily.
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.
If that's the case don't worry about it. We just need to do the sorting once - for the initial /fav (bookmark) page render.
pages/search/search.jsx
Outdated
if (event.keyCode === 27) { // escape key | ||
this.clearSearch(); | ||
} | ||
handleInputChange() { |
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.
If this doesn't do anything else, can you just call updateSearchQuery
from the Debounce input itself?
componentDidMount() { | ||
this.fetchData(this.props.params); | ||
}, | ||
componentWillReceiveProps(nextProps) { | ||
// Reset this.state.entries | ||
this.resetEntries(); |
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 don't think this should be called here. If I type in something new, it shouldn't clear the results while it's doing the search to find the new set; Doing that makes it feel broken because you see a moment of "0 results found", which isn't true. It's especially confusing if you're refining your search and the results you had disappear, and then the same ones reappear.
componentDidMount() { | ||
this.fetchData(this.props.params); | ||
}, | ||
componentWillReceiveProps(nextProps) { | ||
// Reset state | ||
this.resetState(); |
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 should reset state as soon as a new search has been initiated. However, instead of showing "0 results found" I've update that message to Searching for entries that match ‘some_keyword’...
(see render()
below)
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.
But most of the time you won't even have a chance to read that text if the connection is reasonably fast- this process results in a "flicker" of an intermediate state, instead of just changing from one set of results to another. If you want something in the middle, might I suggest a spinner or something to indicate something's happening in the background, without immediately clearing the results?
Think of the case where someone searches for "web" and then decides to add "literacy" to the end of it- it doesn't make sense that the results disappear before reappearing- they should just reduce to entries with the complete string "web literacy" in them.
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 are doing search-as-you-type (we initiate the search when there's no activity happening in the input box for x amount of time), for me having the previous search result stayed while new search is happening in the background can confuse me. If there's no UI change go along with it, as an user I won't know a new search has already been initiated. Having a spinner instead of text Searching for entries that match ‘some_keyword’...
sounds great! But I do think we should clear previous search result while fetching results from Pulse API for the current search keyword.
@alanmoo @kristinashu thoughts? This is what has been implemented in this PR
(note that there are some glitches from the GIF recorder in the last part, where some UI elements seem to collapse together)
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.
But imagine the case where a user is typing just slow enough to trigger multiple searches- they'll have content appearing and disappearing seemingly at random.
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 don't see the problem. I don't think we should reset the state as soon as a user start to type (it should continue to show results for "web" as you add "literacy"). It's ok to have a slight delay. Also don't think we even need a message or a spinner. Message is definitely too quick to read, so maybe a spinner but let's try having nothing and see if users complain.
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.
But imagine the case where a user is typing just slow enough to trigger multiple searches- they'll have content appearing and disappearing seemingly at random.
Um I guess that's the nature of search-as-you-type. We don't start new search on every keystroke so to me that's fair enough. For non slower typers having previous search result stayed can make it look like the system is having problem detecting the new keyword user just typed, especially when network connection is bad.
Anyways, since you two both agree on the same approach I'll update my PR 😄
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 why I suggest a spinner or something to indicate that it's doing something, just not a message you have to read with disappearing 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.
Spinners are nice.
I haven't read this whole thread, but if we get stuck on this, I suggest we consider adding a Go! button. Find as you type is only more awesome if it feels more awesome.
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 why I suggest a spinner or something to indicate that it's doing something, just not a message you have to read with disappearing content.
I'm a bit confused about this part. Did you mean in the transition state (user searched for 'web' first and then 'web literacy') we show
input box: [ web literacy ]
spinner icon
[ matching result #1 for 'web' ] [ result #2 ] [ result #3 ]
[ matching result #4 for 'web' ] [ result #6 ] [ result #6 ]
...
...
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, pretty much. Just put the spinner somewhere that it's not "moving" the content when it appears/disappears. Though if you reduce the debounce time (maybe around 250ms), the whole thing should happen fast enough on reasonable network connections that the spinner doesn't show up for very long at all.
searchResult = (<p>{projects.length} {projects.length > 1 ? `results` : `result`} found for ‘{this.props.params.search}’</p>); | ||
} else { | ||
// We are still waiting to hear back from Pulse API. Let's show some 'searching' notice in the meantime. | ||
searchResult = (<p>Searching for entries that match ‘{this.props.params.search}’...</p>); |
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.
@kristinashu @alanmoo
Searching for entries that match ‘some_keyword’...
<= plz help fixing this sentence that I came up with 😂
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.
How about just "Searching for..."
@alanmoo PR updated. Didn't add spinner but we can always add one later if needed. |
I spun this up and accidentally (but in a reproducible way) got it into an inconsistent state. I clicked search, then typed Looking at my network activity, the search for |
i think i know what went wrong, will update! |
…ted to the current search keyword
this should do... (hopefully) |
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 works, I'm just a bit thrown off by the logic- some things feel like they're still a bit fragile. Can you clarify some of the inline questions?
<div className="project-list"> | ||
{ searchResult } | ||
{ projects ? <div className="projects">{projects}</div> : null } | ||
{ showViewMoreBtn ? <div className="view-more"><button type="button" className="btn" onClick={this.handleViewMoreClick}>View more</button></div> : 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.
This isn't hooked up
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.
nice catch!
pages/search/search.jsx
Outdated
|
||
if (data.next) { | ||
// there are more matched entries in the database we need to fetch | ||
this.fetchData(params, entries, apiPageIndex+1); |
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 do we need to fetch all the pages up front if we've got a load more button later on?
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 didn't realize the
count
property returned from the API is the total count of all matched entries, not just how many entries are in that particularpage
. I thought I had to add them all up to get the total of matched entries count. Can we update the property name to something not so generic? Or at least have some docs for it? -
Not sure how big our db will grow but since we only have ~200 entries now, making one fetch is enough to retrieve all matched entries (
page_size
is set to 996, the closest number to 1000 that's divisible by 6). Currently every time "Load more" is clicked we display 24 more entries. Users see those entries immediately after they click the button. We could change it so that we don't fetch all data at once when we load the page, we make new Pulse API call every time users click on "Load more". This means users will have to wait till we hear back from the call. If we decide to make this change I suggest bumping up " the number of entries in each batch" from 24 to a bigger number. Otherwise it can be quite annoying as users have to go through "click and wait" cycle for many times just to load a few hundreds of entries. /cc @kristinashu @xmatthewx I would like to know your thoughts on this 😉
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.
If we decide to make this change I suggest bumping up " the number of entries in each batch" from 24 to a bigger number.
👍 as long as we don't overwhelm mobile users, loading 48 or more is fine by me. we could also pre-fetch as user scrolls down and nears the bottom.
With page_size of 996, are we fetching that many results or searching that many projects in total? In 2017, we might cross 1000 entries to search. We would need to lift that limit b/c pace will increase as people anticipate new network site. I wouldn't, however, expect to cross 1000 search results this year (for meaningful searches), so a cap on results is fine.
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.
@mmmavis a lot of paginating APIs use count
for the total count, with page
and per_page
for you to figure out exactly which set in [0...count]
you're looking at, so I'd recommend we keep it like that.
As for #2, I'd also still recommend keeping the batches small: if someone's on 3G or worse, getting 200+ entries will still take much longer than is necessary for a user to see a first set of on-page entries loaded in the app.
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 standard API behavior and the docs at the top of the entries endpoint already document the
page_size
, so it doesn't make sense to change this?page_size=
- Number of results on a page. Defaults to 48 -
Yeah I guess I question the purpose of the "Load more" button at all. Only thing I can think of is to avoid forcing the browser to load all those images unless they're wanted. Maybe we do fetch them all at once, and the "load more" button is a gate-keeper which then opens up to show the rest of the results at once? Presumably
pages/search/search.jsx
Outdated
// then use searchQueryInUrl as the search param to fetch data from Pulse API | ||
this.refs.searchInput.setState({value: searchQueryInUrl ? decodeURIComponent(searchQueryInUrl) : ``}); | ||
this.setState({ | ||
keywordBeingSearched: searchQueryInUrl |
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's the purpose of this state variable? It seems like it's only checked on line 85, which could just read the value of the input box directly, no?
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.
oh yea that's better, will update.
let searchResult; | ||
|
||
if (this.state.keywordSearched) { | ||
let numEntriesMatched = this.state.entriesMatched.length; |
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.
If we don't fetch all the entries at the start, we can just use the count
property on the API response here
|
||
// fetch data based on the new params props | ||
if ( newIssueEntered ) { |
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 issues specifically? And where does the issue prop actually get used? This makes me feel like something's not wired up to work best with React. Do you need a higher level component to fetch the data and then pass the data set down to the project list?
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.
from routes.jsx
<Route path="issues">
<IndexRoute component={Issues} />
<Route path=":issue" component={Issue} />
</Route>
And my inline comments in project-list.jsx
// <ProjectList> doesn't always get unmounted and remounted when navigating between pages.
// (e.g., When navigating between the /issue/issue-name pages <Issue><ProjectList></Issue>
// do not get re-mounted since the same components are being rendered.)
// It is treated as passing new props to <ProjectList {...newProps} />
And issue
prop is being used in the next line after the one we are commenting on here
if ( newIssueEntered ) {
// Fetch data based on the new params props to ensure data gets fetched and displayed accordingly.
this.fetchData(nextProps.params);
}
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.
^ is the reasoning why I implemented it that way. Better solution is welcome!
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.
oh yea but we should tackle it in a separate ticket/PR.
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, I see what's going on, but agreed it should be a separate ticket. I'll file 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.
This is fine to merge, I left a few nitpicks that are related to the other ticket I'm going to file a follow up issue for, so they can be addressed there too.
margin-right: -$project-card-side-margin; | ||
} | ||
|
||
.view-more { |
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 use a .text-center
class on the element instead?
// project-list | ||
|
||
.project-list { | ||
.projects { |
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.
If you use row flex-items-center
as the classes instead, it does the same thing (with a minor difference in margin), and then you don't need this CSS. (By consistently using the bootstrap row/column classes, you avoid the margin issue.)
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 will file followup PR
Refactor suggestions detailed in #361 |
Fixes #328