Bug 1520514 - Add memoized selector to add feed data and spocs to layout

This commit is contained in:
Kate Hudson 2019-01-16 14:06:23 -05:00 коммит произвёл GitHub
Родитель 6967f2e7a0
Коммит 7529c0a4bc
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 106 добавлений и 25 удалений

Просмотреть файл

@ -6,6 +6,7 @@ import {ImpressionStats} from "content-src/components/DiscoveryStreamImpressionS
import {List} from "content-src/components/DiscoveryStreamComponents/List/List";
import React from "react";
import {SectionTitle} from "content-src/components/DiscoveryStreamComponents/SectionTitle/SectionTitle";
import {selectLayoutRender} from "content-src/lib/selectLayoutRender";
import {TopSites} from "content-src/components/DiscoveryStreamComponents/TopSites/TopSites";
// According to the Pocket API endpoint specs, `component.properties.items` is a required property with following values:
@ -27,19 +28,19 @@ export class _DiscoveryStreamBase extends React.PureComponent {
return (<SectionTitle />);
case "CardGrid":
return (<CardGrid
title={component.header.title}
title={component.header && component.header.title}
data={component.data}
feed={component.feed}
style={component.properties.style}
items={component.properties.items} />);
case "Hero":
const feed = this.props.DiscoveryStream.feeds[component.feed.url];
const items = Math.min(component.properties.items, MAX_ROWS_HERO);
const rows = feed ? feed.data.recommendations.slice(0, items) : [];
const rows = component.data ? component.data.recommendations.slice(0, items) : [];
return (
<ImpressionStats rows={rows} dispatch={this.props.dispatch} source={component.type}>
<Hero
title={component.header.title}
feed={component.feed}
title={component.header && component.header.title}
data={component.data}
style={component.properties.style}
items={items} />
</ImpressionStats>
@ -56,10 +57,10 @@ export class _DiscoveryStreamBase extends React.PureComponent {
}
render() {
const {layout} = this.props.DiscoveryStream;
const {layoutRender} = this.props.DiscoveryStream;
return (
<div className="discovery-stream ds-layout">
{layout.map((row, rowIndex) => (
{layoutRender.map((row, rowIndex) => (
<div key={`row-${rowIndex}`} className={`ds-column ds-column-${row.width}`}>
{row.components.map((component, componentIndex) => (
<div key={`component-${componentIndex}`}>
@ -73,4 +74,13 @@ export class _DiscoveryStreamBase extends React.PureComponent {
}
}
export const DiscoveryStreamBase = connect(state => ({DiscoveryStream: state.DiscoveryStream}))(_DiscoveryStreamBase);
function transform(state) {
return {
DiscoveryStream: {
...state.DiscoveryStream,
layoutRender: selectLayoutRender(state),
},
};
}
export const DiscoveryStreamBase = connect(transform)(_DiscoveryStreamBase);

Просмотреть файл

@ -1,19 +1,18 @@
import {connect} from "react-redux";
import {DSCard} from "../DSCard/DSCard.jsx";
import React from "react";
export class _CardGrid extends React.PureComponent {
export class CardGrid extends React.PureComponent {
render() {
const feed = this.props.DiscoveryStream.feeds[this.props.feed.url];
const {data} = this.props;
// Handle a render before feed has been fetched by displaying nothing
if (!feed) {
if (!data) {
return (
<div />
);
}
let cards = feed.data.recommendations.slice(0, this.props.items).map((rec, index) => (
let cards = data.recommendations.slice(0, this.props.items).map((rec, index) => (
<DSCard
key={`dscard-${index}`}
image_src={rec.image_src}
@ -31,9 +30,7 @@ export class _CardGrid extends React.PureComponent {
}
}
_CardGrid.defaultProps = {
CardGrid.defaultProps = {
style: `border`,
items: 4, // Number of stories to display
};
export const CardGrid = connect(state => ({DiscoveryStream: state.DiscoveryStream}))(_CardGrid);

Просмотреть файл

@ -1,19 +1,18 @@
import {connect} from "react-redux";
import {DSCard} from "../DSCard/DSCard.jsx";
import React from "react";
export class _Hero extends React.PureComponent {
export class Hero extends React.PureComponent {
render() {
const feed = this.props.DiscoveryStream.feeds[this.props.feed.url];
const {data} = this.props;
// Handle a render before feed has been fetched by displaying nothing
if (!feed) {
if (!data || !data.recommendations) {
return (
<div />
);
}
let [heroRec, ...otherRecs] = feed.data.recommendations;
let [heroRec, ...otherRecs] = data.recommendations;
let truncateText = (text, cap) => `${text.substring(0, cap)}${text.length > cap ? `...` : ``}`;
let cards = otherRecs.slice(1, this.props.items).map((rec, index) => (
@ -48,9 +47,8 @@ export class _Hero extends React.PureComponent {
}
}
_Hero.defaultProps = {
Hero.defaultProps = {
data: {},
style: `border`,
items: 1, // Number of stories to display
};
export const Hero = connect(state => ({DiscoveryStream: state.DiscoveryStream}))(_Hero);

Просмотреть файл

@ -0,0 +1,28 @@
import {createSelector} from "reselect";
export const selectLayoutRender = createSelector(
// Selects layout, feeds, spocs so that we only recompute if
// any of these values change.
[
state => state.DiscoveryStream.layout,
state => state.DiscoveryStream.feeds,
state => state.DiscoveryStream.spocs,
],
// Adds data to each component from feeds. This function only re-runs if one of the inputs change.
// TODO: calculate spocs
function layoutRender(layout, feeds, spocs) {
return layout.map(row => ({
...row,
// Loops through all the components and adds a .data property
// containing data from feeds
components: row.components.map(component => {
if (!component.feed || !feeds[component.feed.url]) {
return component;
}
return {...component, data: feeds[component.feed.url].data};
}),
}));
}
);

5
package-lock.json сгенерированный
Просмотреть файл

@ -10159,6 +10159,11 @@
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
"dev": true
},
"reselect": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz",
"integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA=="
},
"resolve": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz",

Просмотреть файл

@ -13,7 +13,8 @@
"react-dom": "16.2.0",
"react-intl": "2.4.0",
"react-redux": "5.1.0",
"redux": "4.0.1"
"redux": "4.0.1",
"reselect": "4.0.0"
},
"devDependencies": {
"@octokit/rest": "15.17.0",

Просмотреть файл

@ -0,0 +1,42 @@
import {combineReducers, createStore} from "redux";
import {actionTypes as at} from "common/Actions.jsm";
import {reducers} from "common/Reducers.jsm";
import {selectLayoutRender} from "content-src/lib/selectLayoutRender";
const FAKE_LAYOUT = [{width: 3, components: [{type: "foo", feed: {url: "foo.com"}}]}];
const FAKE_FEEDS = {"foo.com": {data: ["foo", "bar"]}};
describe("selectLayoutRender", () => {
let store;
beforeEach(() => {
store = createStore(combineReducers(reducers));
});
it("should return an empty array given initial state", () => {
const result = selectLayoutRender(store.getState());
assert.deepEqual(result, []);
});
it("should add .data property from feeds to each compontent in .layout", () => {
store.dispatch({type: at.DISCOVERY_STREAM_LAYOUT_UPDATE, data: {layout: FAKE_LAYOUT}});
store.dispatch({type: at.DISCOVERY_STREAM_FEEDS_UPDATE, data: FAKE_FEEDS});
const result = selectLayoutRender(store.getState());
assert.lengthOf(result, 1);
assert.propertyVal(result[0], "width", 3);
assert.deepEqual(result[0].components[0], {type: "foo", feed: {url: "foo.com"}, data: ["foo", "bar"]});
});
it("should return layout property without data if feed isn't available", () => {
store.dispatch({type: at.DISCOVERY_STREAM_LAYOUT_UPDATE, data: {layout: FAKE_LAYOUT}});
store.dispatch({type: at.DISCOVERY_STREAM_FEEDS_UPDATE, data: {}});
const result = selectLayoutRender(store.getState());
assert.lengthOf(result, 1);
assert.propertyVal(result[0], "width", 3);
assert.deepEqual(result[0].components[0], FAKE_LAYOUT[0].components[0]);
});
});