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
Add a categories page, component, and category search support #1237
Conversation
8ef6093
to
10925eb
Compare
225bfb8
to
9f49530
Compare
Implement a new /v3/categories/ endpoint for our static categories. Fixes #3676, supports mozilla/addons-frontend#1237
The API is now available at |
6f75ba9
to
b86c20c
Compare
b86c20c
to
51bf303
Compare
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.
You've got a .DS_Store
and it looks like the Fira fonts you added aren't being used?
I mostly skimmed the tests because I've been looking at this for a while.
padding: 0; | ||
} | ||
|
||
.Categories-listItem { |
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 we've been using PascalCase-kebab-case
in the past.
padding: 0; | ||
width: 95%; | ||
|
||
a { |
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've been trying not to use bare element selectors like this but it seems that everyone else is fine with it. I'd prefer .Categories-link
but it's up to you.
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.
No your way is totally better; I've been trying for that too and just missed this one. It means less nesting which is nice.
@@ -0,0 +1,39 @@ | |||
// import React, { PropTypes } from 'react'; |
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.
💣💥 🔥
import SearchResult from './SearchResult'; | ||
|
||
|
||
export class SearchPageBase extends React.Component { | ||
export default class SearchPage extends React.Component { |
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.
Yay, less redux in components! 👏
render() { | ||
const { CategoriesComponent, addonType } = this.props; | ||
return ( | ||
<div className="Categories-Page" ref={(ref) => { this.container = ref; }}> |
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 component needed? It looks like this could export connect(...)(Categories)
if you want to keep the redux out of the Categories
file.
Actually is this file used at all? It looks like Categories
has the same asyncConnect()
statement.
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 created it solely because it seems like a page and I figured it should have a container in addition to a component.
That said you're right it can be simplified a lot, I've tidied it up where the container does the asyncConnect
and the component doesn't, and removed the HTML from the container. That makes sense. 👍
dispatch, page, api, auth = false, addonType, category, query, | ||
}) { | ||
// If none of the optional search params are found, we aren't searching for | ||
if ([addonType, category, query].filter((param) => { |
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 I've seen this snippet three times now, it should be pulling from the state as I mentioned above.
} = this.props; | ||
|
||
let searchResults; | ||
let messageText; | ||
const searchParamIsPresent = [ |
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 could use the value from state, too.
}; | ||
const doingSearch = Object.keys(queryStringMap).filter((queryKey) => { | ||
return (location.query[queryKey] !== undefined && | ||
location.query[queryKey] === state.search[queryStringMap[queryKey]]); |
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 this check the URL? Do we want this to be true at /en-US/firefox/addons/ublock-origin/?q=huh
?
{ | ||
application: 'android', | ||
name: 'Games', | ||
slug: 'games', |
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 probably have some slugs that aren't just lowercase of the name to be safe.
|
||
|
||
describe('Home', () => { | ||
// function render(props) { |
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, you’re right, in which case now I’m confused. I’ll check it out.
|
Well the lack of “s” is less code/confusion; the “s” is prettier URLs. But
it’s just a .replace(/s$/, ‘') call right now, so I think it’s worth it.
|
Okay, updated things and I think I addressed all the comments. Ready for another r? |
Re: URLS, we can't deviate from the urls used on AMO until we've done desktop. I've filed mozilla/addons#9947 to check this along with the rationale behind 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 only made it about half way through this but I think not using the search component for categories will make a lot of changes so I'm going to stop here for now.
Sorry I missed this the first time (and for the slow review this time), I think I saw the component and wasn't sure what it was for but didn't connect the dots to what was happening.
It seems like there's a lot of logic for categories and search that once combined with the logic to differentiate the two makes the search really bloated and hard to follow. I don't think splitting the two will cause much duplication, if any, and it should make the components much simpler.
@@ -12,6 +12,7 @@ | |||
}, | |||
"parser": "babel-eslint", | |||
"rules": { | |||
"arrow-body-style": ["off", "always"], |
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 generally say do this separately, especially since we had some discussion in an issue about it. Do you mind splitting this out into its own commit before you merge?
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 an update caused it to start yelling at me so I changed it (or maybe it was just a tonne of warnings).
I will do it if it doesn't cause failures though, sure thing.
|
||
|
||
export function filterAndSortCategories(categories, addonType, clientApp) { | ||
return categories.filter((category) => { |
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 really prefer the ()
form here but I seem to be the only one :(
return categories.filter((category) => (
category.type === addonType && category.application === clientApp;
)).sort((a, b) => a.name > b.name);
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 find the implicit returns harder to follow and also just prefer all functions look the same for consistency. I find implicit return JS to be harder to scan.
addonType, categories, clientApp, error, loading, i18n, | ||
} = this.props; | ||
|
||
if (loading && !categories.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.
Should we always do this instead of only when we don't have categories? Why would we have categories and still be loading?
} = this.props; | ||
|
||
if (loading && !categories.length) { | ||
return <div>{i18n.gettext('Loading...')}</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.
Maybe these <div>
s should have the Category
class, and potentially a <p>
inside of them. That way any general layout stuff is the same.
return ( | ||
<li className="Categories-list-item"> | ||
<Link className="Categories-link" | ||
to={{ pathname: '/search/', query: queryParams }}> |
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.
Do we not have a category detail page? There's currently one at https://addons.mozilla.org/en-US/firefox/extensions/search-tools/ and I think this makes way more sense than going to search.
If there's an issue for this that's cool but I feel we should have a detail page that uses this search query in the background.
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 we should have that–there are URL parity bugs and this would be a case where I should add it.
return null; | ||
} | ||
|
||
const categoryMatch = categories.filter((category) => { |
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 feels awkward to keep filtering the categories all over. Can you store them as { firefox: { extensions: { search: [] } } }
instead?
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, fair point.
return (category.application === clientApp && category.slug === slug && | ||
category.type === addonType); | ||
}); | ||
const category = categoryMatch.length ? categoryMatch[0] : false; |
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 an Array.prototype.find()
that would remove the need for this but restructuring how we store categories is probably better.
pathname="/search/" query={{ q: query }} showPages={0} /> : []; | ||
|
||
return ( | ||
<div className="search-page"> | ||
<SearchResults results={results} query={query} loading={loading} | ||
count={count} ResultComponent={SearchResult} /> | ||
<SearchResults CategoryInfoComponent={CategoryInfoComponent} |
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.
Category info doesn't feel related to search. I'd say it should be its own page that just calls search for its API request.
@@ -4,23 +4,23 @@ import { | |||
SEARCH_FAILED, | |||
} from 'core/constants'; | |||
|
|||
export function searchStart(query, page) { | |||
export function searchStart({ page, query, addonType, app, category }) { |
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 really feels like it's modifying search too much.
Ah, I see. I figured extending the search and allowing it the flexibility to be the one place we'd look for add-ons would be the best option, allowing for really specific searches by both users and admins in the future. We can totally split it into its own component with likely little duplication, just figured it might be cool to allow a search page with lots of query options. Parts like including category info in the search page is fair enough, but I felt like any listing of add-ons was essentially a search and would make sense to be treated as one. My gut reaction is I would rather thing extend search than become its own component. I'll have another look and see if I'm wrong on that and it'd be cleaner separated or if there's at least more than could be split out. |
5202a01
to
48e7f81
Compare
}); | ||
} | ||
|
||
export function search({ api, page, query, auth = false, category, addonType }) { |
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 I'd like this to be more generic for the extra filters. Essentially thinking of it as search({ query, page, filters })
and filters
would have the category/addon type in it. Once that's done it might be nicer to wrap this in a category()
function so it's clear how to get category listings.
export function category({ category, addonType, filters = {}, ...args }) {
return search({ ...args, filters: { ...filters, category, addonType } });
}
|
||
|
||
const initialState = { | ||
categories: [], |
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 commented earlier about this but I'll add something since we just discussed some things on IRC.
I think this should be more like:
{
categories: {
adblocking: {
firefox: {
extensions: {
count: 20,
results: ['abp', 'ublock', ...],
page: 1,
},
},
},
abstract: {
firefox: {
themes: {
count: 123897,
results: ['blue', 'fractal', 'liberty', ...],
page: 33,
},
},
},
},
}
This is really nested but I'm not sure if there'd be a good way to flatten it. The bit inside extensions
or themes
could be a "sub-reducer" that is what we should be using as well in the search reducer. The order could be different too. themes.firefox.abstract
or firefox.extensions.adblocking
.
This lacks some style bits which I want to address later. This commit has become quite massive. Implements category component, category page container, and adds category support to search.
48e7f81
to
db84db9
Compare
Superseded by mozilla/addons-server#1376, which is now merged. |
Still a WIP and missing some tests, though I'm gonna finish that soon.Will need mozilla/addons#3588 to finish.This adds category support to the frontend. It's a beast of a patch and I'm totally sorry. I owe all reviewers beer at Hawaii.
There are a few style issues that I will happily tweak afterward, but I thought getting this merged before it ballooned further was wise.
I also realised that there isn't a paginator for the categories search results. I can add that here but again would be happy to do it in a follow-up patch to save review bloat.
Screenshots:
r?