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 category page (close #1378) #1398

Merged
merged 1 commit into from
Nov 23, 2016
Merged

Add category page (close #1378) #1398

merged 1 commit into from
Nov 23, 2016

Conversation

tofumatt
Copy link
Contributor

Add the actual category URL (matches addons-server) including pagination.

Screenshot:

nov-21-2016 12-21-02

Copy link
Contributor

@kumar303 kumar303 left a comment

Choose a reason for hiding this comment

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

Nice, I like how the category component extends the search component. I had some questions and requested a few changes.

convertFiltersToQueryParams(filters);
const paginator = count && hasSearchParams > 0 ? (
<Paginate LinkComponent={LinkComponent} count={count} currentPage={page}
pathname={pathname} queryParams={queryParams} showPages={0} />
Copy link
Contributor

Choose a reason for hiding this comment

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

why is showPages always 0? Is that because the UI shouldn't ever show links to numbered pages?

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 use the numbered pages in the admin but not AMO.

It's a bit of a holdover, arguably showPages={0} should be the default. Filed: #1405



export function mapStateToProps(state, ownProps) {
const filters = {
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 it would make sense to move this definition and other relevant ones into the block if (filtersMatch(... since that's the only place they are needed. A function like mapStateToProps() will run on every application state change so it's a bit sensitive.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But these filters are declared here to compare them with state and make sure everything matches. They're passed to if (filtersMatch([...])) {, so moving them into there would mean duplication.

They're only passed to the state if they match.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, ok, I see on a second look that all of these do need to be defined outside of the if block.

if (!isLoaded({ state: state.search, page, filters })) {
return performSearch({ dispatch, page, api: state.api, auth: state.auth, filters });
}
return true;
Copy link
Contributor

Choose a reason for hiding this comment

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

even though it's supported by asyncConnect, I think it's bad practice to mix promise and non-promise return values. I'd rather see: return Promise.resolve(true) or maybe simply Promise.resolve() if that's enough.

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 agree actually, as it makes testing weird too. That said:

it-was-like-that-when-i-got-here

I've filed an issue for it (#1403), as changing this across the app would be out-of-scope for the PR.

});

assert.deepEqual(props, {
hasSearchParams: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

this parameter will be overidden by ...state.search. Maybe this object should begin with ...state.search instead of end with it?



describe('CategoryPage.mapStateToProps()', () => {
const state = {
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 it would be less fragile to create this state by dispatching real actions. It should be just as easy to do. Example: https://github.com/mozilla/addons-frontend/issues/1371

queryParams: { page: 1 },
});
});
});
Copy link
Contributor

Choose a reason for hiding this comment

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

This appears to be missing a test for the case when state.search is empty (i.e. {}).

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 don't think that should ever happen, as it's set by searchStart. Am I missing a path to an empty search state?

Copy link
Contributor

Choose a reason for hiding this comment

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

The component may render with state.search = {} for a split second if the API response is slow but, no worries, we could always add the test later if a bug appears with handling empty state.

@@ -16,3 +21,77 @@ describe('searchUtils mapStateToProps()', () => {
assert.deepEqual(props, { hasSearchParams: false });
});
});

describe('searchUtils loadCategoryResultsIfNeeded()', () => {
const state = {
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 also like to this state built by dispatching real actions, if possible

slug: 'ad-block',
};
assert.strictEqual(
loadCategoryResultsIfNeeded({ store, location, params }), true);
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 this test would be better if it verified that mockApi is never called, which is a stronger indicator that data is not fetched from the API.

return true;
}

export function loadCategoryResultsIfNeeded({ store: { dispatch, getState }, location, params }) {
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 be shortened slightly to loadByCategoryIfNeeded

category: params.slug,
clientApp: params.application,
};

if (!isLoaded({ state: state.search, page, filters })) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do the search results need to be cached? I think it should make an API request every time. Otherwise, the single page app will go stale -- the only way a user can obtain newly added results is to refresh the page. The browser will already cache the API call as needed so I don't really see the value in adding a new caching layer on top of that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. Again, it was like that when I got here; the search page currently caches results. When flipping through paginated results it makes going back and forth between pages quick.

I've filed #1404, but I'll remove it from this bit of code because why not and I agree; we can always add it back.

@tofumatt
Copy link
Contributor Author

Okay, I think I fixed all the issues (mostly regarding state testing) in the PR, and created issues for the things that were slightly out-of-scope. Ready for another r?

@tofumatt
Copy link
Contributor Author

Odd: https://travis-ci.org/mozilla/addons-frontend/builds/177831776 is failing but passing locally, and the error is quite strange.

@mstriemer
Copy link
Contributor

Try rebasing with master. That's what I was running into yesterday and switching back to npm seemed to fix it.

@tofumatt
Copy link
Contributor Author

tofumatt commented Nov 22, 2016 via email

Copy link
Contributor

@kumar303 kumar303 left a comment

Choose a reason for hiding this comment

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

r+wc

Thanks for filing all the fixup issues.

if (!isLoaded({ state: state.search, page, filters })) {
return performSearch({ dispatch, page, api: state.api, auth: state.auth, filters });
}
return true;
}

export function loadByCategoryIfNeeded(
Copy link
Contributor

Choose a reason for hiding this comment

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

This name no longer makes sense :) I think loadByCategory would be fine.

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 making another API request on the client after JS loading on initial render? I think that's why we've normally included the caching.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, maybe it still needs caching. I was hoping to achieve the rules defined here: https://github.com/mozilla/addons-frontend/issues/1404#issuecomment-262306143

const store = createStore();
store.dispatch(searchStart({ filters }));
const mismatchedState = store.getState();
mismatchedState.search.filters.clientApp = 'nothing';
Copy link
Contributor

Choose a reason for hiding this comment

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

Using dispatch (like above) is the way to do it IMO. By using dispatch you are not coupling this code with the raw state representation. Here would be a way to rewrite it:

store.dispatch(searchStart({ filters: { ...filters, clientApp: 'nothing' } }));
const mismatchedState = store.getState();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, good point. Will do.

queryParams: { page: 1 },
});
});
});
Copy link
Contributor

Choose a reason for hiding this comment

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

The component may render with state.search = {} for a split second if the API response is slow but, no worries, we could always add the test later if a bug appears with handling empty state.

@@ -16,3 +20,38 @@ describe('searchUtils mapStateToProps()', () => {
assert.deepEqual(props, { hasSearchParams: false });
});
});

describe('searchUtils loadByCategoryIfNeeded()', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

loadByCategoryIfNeeded would need to be renamed too

@tofumatt tofumatt merged commit 53e5f4c into master Nov 23, 2016
@tofumatt tofumatt deleted the categories-page-1378 branch November 23, 2016 16:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants