Skip to content
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

Closed
wants to merge 6 commits into from

Conversation

tofumatt
Copy link
Contributor

@tofumatt tofumatt commented Oct 14, 2016

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:

screen shot 2016-11-02 at 16 19 45

screen shot 2016-11-02 at 16 19 49

r?

@tofumatt tofumatt force-pushed the implement-categories-1183 branch 2 times, most recently from 8ef6093 to 10925eb Compare October 24, 2016 19:50
EnTeQuAk added a commit to mozilla/addons-server that referenced this pull request Oct 26, 2016
EnTeQuAk added a commit to mozilla/addons-server that referenced this pull request Oct 26, 2016
Implement a new /v3/categories/ endpoint for our static categories.

Fixes #3676, supports mozilla/addons-frontend#1237
@EnTeQuAk
Copy link
Contributor

EnTeQuAk commented Oct 27, 2016

The API is now available at /api/v3/addons/categories/, e.g https://addons-dev.allizom.org/api/v3/addons/categories/ for -dev.

@tofumatt tofumatt force-pushed the implement-categories-1183 branch 2 times, most recently from 6f75ba9 to b86c20c Compare November 2, 2016 16:12
@tofumatt tofumatt changed the title WIP: Add a categories page, component, and category search support Add a categories page, component, and category search support Nov 2, 2016
Copy link
Contributor

@mstriemer mstriemer left a 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 {
Copy link
Contributor

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 {
Copy link
Contributor

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.

Copy link
Contributor Author

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';
Copy link
Contributor

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 {
Copy link
Contributor

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; }}>
Copy link
Contributor

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.

Copy link
Contributor Author

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) => {
Copy link
Contributor

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 = [
Copy link
Contributor

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]]);
Copy link
Contributor

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',
Copy link
Contributor

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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥

@tofumatt
Copy link
Contributor Author

tofumatt commented Nov 3, 2016 via email

@tofumatt
Copy link
Contributor Author

tofumatt commented Nov 3, 2016 via email

@tofumatt
Copy link
Contributor Author

tofumatt commented Nov 3, 2016

Okay, updated things and I think I addressed all the comments. Ready for another r?

@muffinresearch
Copy link
Contributor

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.

Copy link
Contributor

@mstriemer mstriemer left a 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"],
Copy link
Contributor

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?

Copy link
Contributor Author

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) => {
Copy link
Contributor

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);

Copy link
Contributor Author

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) {
Copy link
Contributor

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>;
Copy link
Contributor

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 }}>
Copy link
Contributor

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.

Copy link
Contributor Author

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) => {
Copy link
Contributor

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?

Copy link
Contributor Author

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;
Copy link
Contributor

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}
Copy link
Contributor

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 }) {
Copy link
Contributor

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.

@tofumatt
Copy link
Contributor Author

tofumatt commented Nov 7, 2016

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.

});
}

export function search({ api, page, query, auth = false, category, addonType }) {
Copy link
Contributor

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: [],
Copy link
Contributor

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.
@tofumatt
Copy link
Contributor Author

Superseded by mozilla/addons-server#1376, which is now merged.

@tofumatt tofumatt closed this Nov 17, 2016
@tofumatt tofumatt deleted the implement-categories-1183 branch November 17, 2016 17:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants