From 58bde92d68f10e83594866dc277425c12c50d11b Mon Sep 17 00:00:00 2001 From: User Date: Sat, 26 May 2018 00:49:20 +0300 Subject: [PATCH 1/5] Add tests for react-virtulized-table --- .../events/events-table-virtualized.js | 32 +++++++++-- .../events/events-table-virtualized.test.js | 57 +++++++++++++++++++ admin/src/config.js | 16 ------ 3 files changed, 83 insertions(+), 22 deletions(-) create mode 100644 admin/src/components/events/events-table-virtualized.test.js delete mode 100644 admin/src/config.js diff --git a/admin/src/components/events/events-table-virtualized.js b/admin/src/components/events/events-table-virtualized.js index eb5ba6c..aba42c5 100644 --- a/admin/src/components/events/events-table-virtualized.js +++ b/admin/src/components/events/events-table-virtualized.js @@ -11,6 +11,11 @@ import Loader from '../common/loader' import { Table, Column } from 'react-virtualized' import 'react-virtualized/styles.css' +export const width = 900 +export const columnWidth = 300 +export const height = 420 +export const rowHeight = 40 + export class EventsTable extends Component { static propTypes = {} @@ -24,20 +29,35 @@ export class EventsTable extends Component { return ( - - - + ( +
+ {cellData} +
+ )} + dataKey="title" + width={columnWidth} + /> + +
) } rowGetter = ({ index }) => this.props.events[index] + + handleRowClick = (event) => () => { + this.props.handleSelect(event.uid) + } } export default connect( diff --git a/admin/src/components/events/events-table-virtualized.test.js b/admin/src/components/events/events-table-virtualized.test.js new file mode 100644 index 0000000..a067d93 --- /dev/null +++ b/admin/src/components/events/events-table-virtualized.test.js @@ -0,0 +1,57 @@ +import React from 'react' +import { + EventsTable, + width, + height, + rowHeight +} from './events-table-virtualized' +import { shallow, mount } from 'enzyme' +import { Table, Column } from 'react-virtualized' + +import Loader from '../common/loader' +import eventsMocks from '../../mocks/conferences' + +const defaultOverscanRowCount = 1 + +const events = eventsMocks.map((event) => ({ + ...event, + uid: Math.random().toString() +})) + +describe('EventsVirtualizedTable', () => { + it('should render loader', () => { + const container = shallow() + + expect(container.contains()).toBe(true) + }) + + it('should render visible events', () => { + const container = mount() + + expect(container.find('.test--event-list_item').length).toEqual( + Math.round(height / rowHeight) + defaultOverscanRowCount + ) + }) + + it('should fetch all events', (done) => { + shallow( done()} />) + }) + + it('should select an event', () => { + let selectedEventId = null + + const container = mount( + (selectedEventId = id)} + /> + ) + + container + .find('.test--event-list_item') + .first() + .simulate('click') + + expect(selectedEventId).toEqual(events[0].uid) + }) +}) diff --git a/admin/src/config.js b/admin/src/config.js deleted file mode 100644 index aa22b3b..0000000 --- a/admin/src/config.js +++ /dev/null @@ -1,16 +0,0 @@ -import firebase from 'firebase/app' -import 'firebase/auth' -import 'firebase/database' - -export const appName = 'advreact-10-05' - -export const config = { - apiKey: 'AIzaSyCbMQM0eQUSQ0SuLVAu9ZNPUcm4rdbiB8U', - authDomain: `${appName}.firebaseapp.com`, - databaseURL: `https://${appName}.firebaseio.com`, - projectId: appName, - storageBucket: '', - messagingSenderId: '1094825197832' -} - -firebase.initializeApp(config) From 413187c3d1313bd7094a5f0c353d6741d8d8283d Mon Sep 17 00:00:00 2001 From: User Date: Sat, 26 May 2018 08:38:26 +0300 Subject: [PATCH 2/5] Completed the tests --- .../events/events-table-virtualized.test.js | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/admin/src/components/events/events-table-virtualized.test.js b/admin/src/components/events/events-table-virtualized.test.js index a067d93..1400434 100644 --- a/admin/src/components/events/events-table-virtualized.test.js +++ b/admin/src/components/events/events-table-virtualized.test.js @@ -3,7 +3,8 @@ import { EventsTable, width, height, - rowHeight + rowHeight, + columnWidth } from './events-table-virtualized' import { shallow, mount } from 'enzyme' import { Table, Column } from 'react-virtualized' @@ -33,6 +34,23 @@ describe('EventsVirtualizedTable', () => { ) }) + it('should render 3 colunms', () => { + const container = shallow() + + expect(container.children().length).toEqual(3) + }) + + it('should render first column with key title', () => { + const container = shallow() + + expect( + container + .children() + .find({ dataKey: 'title' }) + .exists() + ).toEqual(true) + }) + it('should fetch all events', (done) => { shallow( done()} />) }) From 44513d06cbf8504b138793f6bbb127bc3c8444a8 Mon Sep 17 00:00:00 2001 From: User Date: Sat, 26 May 2018 21:54:10 +0300 Subject: [PATCH 3/5] Add lazy-loading for events table --- .../events-table-virtualized-lazy-loading.js | 187 ++++++++++++++++++ admin/src/routes/events-page.js | 2 +- 2 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 admin/src/components/events/events-table-virtualized-lazy-loading.js diff --git a/admin/src/components/events/events-table-virtualized-lazy-loading.js b/admin/src/components/events/events-table-virtualized-lazy-loading.js new file mode 100644 index 0000000..c1e7aea --- /dev/null +++ b/admin/src/components/events/events-table-virtualized-lazy-loading.js @@ -0,0 +1,187 @@ +import React, { Component } from 'react' +import { connect } from 'react-redux' +import { + fetchAllEvents, + toggleSelection as handleSelect, + eventListSelector, + loadedSelector, + loadingSelector +} from '../../ducks/events' +import Loader from '../common/loader' +import { InfiniteLoader, AutoSizer, List } from 'react-virtualized' +import 'react-virtualized/styles.css' + +export const width = 900 +export const columnWidth = 300 +export const height = 420 +export const rowHeight = 40 + +const STATUS_LOADING = 1 +const STATUS_LOADED = 2 + +export class EventsTable extends Component { + state = { + loadedRowCount: 0, + loadedRowsMap: {}, + loadingRowCount: 0 + } + static propTypes = {} + + componentDidMount() { + this.props.fetchAllEvents && this.props.fetchAllEvents() + } + + componentWillUnmount() { + Object.keys(this._timeoutIdMap).forEach((timeoutId) => { + clearTimeout(timeoutId) + }) + } + + render() { + const { loading, events } = this.props + const { loadedRowCount, loadingRowCount } = this.state + if (loading) return + return ( +
+
+ + +
+ {loadingRowCount} loading, {loadedRowCount} loaded +
+
+ + + {({ onRowsRendered, registerChild }) => ( + + {({ width }) => ( + + )} + + )} + {/* + ( +
+ {cellData} +
+ )} + dataKey="title" + width={columnWidth} + /> + + +
*/} +
+
+ ) + } + + // rowGetter = ({ index }) => this.props.events[index] + // + // handleRowClick = (event) => () => { + // this.props.handleSelect(event.uid) + // } + + _timeoutIdMap = () => {} + + _clearData = () => { + this.setState({ + loadedRowCount: 0, + loadedRowsMap: {}, + loadingRowCount: 0 + }) + } + + _isRowLoaded = ({ index }) => { + const { loadedRowsMap } = this.state + return !!loadedRowsMap[index] // STATUS_LOADING or STATUS_LOADED + } + + _loadMoreRows = ({ startIndex, stopIndex }) => { + const { loadedRowsMap, loadingRowCount } = this.state + const increment = stopIndex - startIndex + 1 + + for (var i = startIndex; i <= stopIndex; i++) { + loadedRowsMap[i] = STATUS_LOADING + } + + this.setState({ + loadingRowCount: loadingRowCount + increment + }) + + const timeoutId = setTimeout(() => { + const { loadedRowCount, loadingRowCount } = this.state + + delete this._timeoutIdMap[timeoutId] + + for (var i = startIndex; i <= stopIndex; i++) { + loadedRowsMap[i] = STATUS_LOADED + } + + this.setState({ + loadingRowCount: loadingRowCount - increment, + loadedRowCount: loadedRowCount + increment + }) + + promiseResolver() + }, 1000 + Math.round(Math.random() * 2000)) + + this._timeoutIdMap[timeoutId] = true + + let promiseResolver + + return new Promise((resolve) => { + promiseResolver = resolve + }) + } + + _rowRenderer = ({ index, key, style }) => { + const { events } = this.props + const { loadedRowsMap } = this.state + + const row = events[index] + let content + // console.log(row.size) + + if (loadedRowsMap[index] === STATUS_LOADED) { + content = row.title + } else { + content =
+ } + + return
{content}
+ } +} + +export default connect( + (state) => ({ + events: eventListSelector(state), + loading: loadingSelector(state), + loaded: loadedSelector(state) + }), + { fetchAllEvents, handleSelect } +)(EventsTable) diff --git a/admin/src/routes/events-page.js b/admin/src/routes/events-page.js index 2472593..a74e7bb 100644 --- a/admin/src/routes/events-page.js +++ b/admin/src/routes/events-page.js @@ -1,5 +1,5 @@ import React, { Component } from 'react' -import EventsTable from '../components/events/events-table-virtualized' +import EventsTable from '../components/events/events-table-virtualized-lazy-loading' import SelectedEvents from '../components/events/selected-events' class EventsPage extends Component { From 24ce01a3dd7aa1ed9c9e2ada5f82ee23cae05702 Mon Sep 17 00:00:00 2001 From: User Date: Sun, 27 May 2018 10:45:21 +0300 Subject: [PATCH 4/5] Add temlpate lazy-loading-table --- .../events-table-virtualized-lazy-loading.js | 165 +++++++++--------- admin/src/ducks/events.js | 80 ++++++++- 2 files changed, 157 insertions(+), 88 deletions(-) diff --git a/admin/src/components/events/events-table-virtualized-lazy-loading.js b/admin/src/components/events/events-table-virtualized-lazy-loading.js index c1e7aea..204065e 100644 --- a/admin/src/components/events/events-table-virtualized-lazy-loading.js +++ b/admin/src/components/events/events-table-virtualized-lazy-loading.js @@ -1,11 +1,16 @@ import React, { Component } from 'react' import { connect } from 'react-redux' import { - fetchAllEvents, + fetchEventsWithPagination, toggleSelection as handleSelect, eventListSelector, loadedSelector, - loadingSelector + loadingSelector, + loadedRowCount, + loadingRowCount, + loadedRowsMap, + clearData, + changeLoadingInfo } from '../../ducks/events' import Loader from '../common/loader' import { InfiniteLoader, AutoSizer, List } from 'react-virtualized' @@ -20,15 +25,16 @@ const STATUS_LOADING = 1 const STATUS_LOADED = 2 export class EventsTable extends Component { - state = { - loadedRowCount: 0, - loadedRowsMap: {}, - loadingRowCount: 0 - } + // state = { + // // loadedRowCount: 0, + // loadedRowsMap: {} + // // loadingRowCount: 0 + // } static propTypes = {} componentDidMount() { - this.props.fetchAllEvents && this.props.fetchAllEvents() + this.props.fetchEventsWithPagination && + this.props.fetchEventsWithPagination() } componentWillUnmount() { @@ -38,134 +44,114 @@ export class EventsTable extends Component { } render() { - const { loading, events } = this.props - const { loadedRowCount, loadingRowCount } = this.state - if (loading) return + const { + loading, + // loaded, + events, + clearData, + loadedRowCount, + loadingRowCount + } = this.props + return (
- + +
+
{loadingRowCount} loading, {loadedRowCount} loaded
+
+
- - - {({ onRowsRendered, registerChild }) => ( - - {({ width }) => ( - - )} - - )} - {/* - ( -
- {cellData} -
- )} - dataKey="title" - width={columnWidth} - /> - - -
*/} -
+ {loading && !loadedRowCount ? ( + + ) : ( + + {({ onRowsRendered, registerChild }) => ( + + {({ width }) => ( + + )} + + )} + + )}
) } - // rowGetter = ({ index }) => this.props.events[index] - // - // handleRowClick = (event) => () => { - // this.props.handleSelect(event.uid) - // } + handleRowClick = (event) => () => { + this.props.handleSelect(event.uid) + } _timeoutIdMap = () => {} - _clearData = () => { - this.setState({ - loadedRowCount: 0, - loadedRowsMap: {}, - loadingRowCount: 0 - }) - } - _isRowLoaded = ({ index }) => { - const { loadedRowsMap } = this.state + const { loadedRowsMap } = this.props return !!loadedRowsMap[index] // STATUS_LOADING or STATUS_LOADED } _loadMoreRows = ({ startIndex, stopIndex }) => { - const { loadedRowsMap, loadingRowCount } = this.state + const { loadedRowsMap, loadingRowCount } = this.props const increment = stopIndex - startIndex + 1 for (var i = startIndex; i <= stopIndex; i++) { loadedRowsMap[i] = STATUS_LOADING } - this.setState({ + this.props.changeLoadingInfo({ loadingRowCount: loadingRowCount + increment }) const timeoutId = setTimeout(() => { - const { loadedRowCount, loadingRowCount } = this.state - + const { loadedRowCount, loadingRowCount } = this.props delete this._timeoutIdMap[timeoutId] for (var i = startIndex; i <= stopIndex; i++) { loadedRowsMap[i] = STATUS_LOADED } - this.setState({ + this.props.changeLoadingInfo({ loadingRowCount: loadingRowCount - increment, loadedRowCount: loadedRowCount + increment }) - promiseResolver() + console.log({ size: loadingRowCount, startIndex, stopIndex }) + + return promiseResolver() }, 1000 + Math.round(Math.random() * 2000)) this._timeoutIdMap[timeoutId] = true - let promiseResolver + let promiseResolver = () => { + this.props.fetchEventsWithPagination() + } return new Promise((resolve) => { - promiseResolver = resolve + resolve(promiseResolver) }) } _rowRenderer = ({ index, key, style }) => { - const { events } = this.props - const { loadedRowsMap } = this.state + const { events, loadedRowsMap } = this.props const row = events[index] let content - // console.log(row.size) if (loadedRowsMap[index] === STATUS_LOADED) { content = row.title @@ -173,7 +159,11 @@ export class EventsTable extends Component { content =
} - return
{content}
+ return ( +
+ {content} +
+ ) } } @@ -181,7 +171,10 @@ export default connect( (state) => ({ events: eventListSelector(state), loading: loadingSelector(state), - loaded: loadedSelector(state) + loaded: loadedSelector(state), + loadedRowCount: loadedRowCount(state), + loadingRowCount: loadingRowCount(state), + loadedRowsMap: loadedRowsMap(state) }), - { fetchAllEvents, handleSelect } + { fetchEventsWithPagination, handleSelect, clearData, changeLoadingInfo } )(EventsTable) diff --git a/admin/src/ducks/events.js b/admin/src/ducks/events.js index 74107b8..dc0012d 100644 --- a/admin/src/ducks/events.js +++ b/admin/src/ducks/events.js @@ -14,7 +14,12 @@ const prefix = `${appName}/${moduleName}` export const FETCH_ALL_REQUEST = `${prefix}/FETCH_ALL_REQUEST` export const FETCH_ALL_START = `${prefix}/FETCH_ALL_START` export const FETCH_ALL_SUCCESS = `${prefix}/FETCH_ALL_SUCCESS` +export const FETCH_PAGINATION_EVENTS_REQUEST = `${prefix}/FETCH_PAGINATION_EVENTS_REQUEST` +export const FETCH_PAGINATION_EVENTS_START = `${prefix}/FETCH_PAGINATION_EVENTS_START` +export const FETCH_PAGINATION_EVENTS_SUCCESS = `${prefix}/FETCH_PAGINATION_EVENTS_SUCCESS` export const TOGGLE_SELECTION = `${prefix}/TOGGLE_SELECTION` +export const CLEAR_DATA = `${prefix}/CLEAR_DATA` +export const CHANGE_LOADING_INFO = `${prefix}/CHANGE_LOADING_INFO` /** * Reducer @@ -23,7 +28,10 @@ export const ReducerRecord = Record({ loading: false, loaded: false, selected: new OrderedSet(), - entities: new OrderedMap() + entities: new OrderedMap(), + loadedRowCount: 0, + loadingRowCount: 0, + loadedRowsMap: {} }) export const EventRecord = Record({ @@ -41,9 +49,14 @@ export default function reducer(state = new ReducerRecord(), action) { switch (type) { case FETCH_ALL_START: + case FETCH_PAGINATION_EVENTS_START: return state.set('loading', true) + case CHANGE_LOADING_INFO: + return state.merge(payload) + case FETCH_ALL_SUCCESS: + case FETCH_PAGINATION_EVENTS_SUCCESS: return state .set('loading', false) .set('loaded', true) @@ -58,6 +71,13 @@ export default function reducer(state = new ReducerRecord(), action) { : selected.add(payload.uid) ) + case CLEAR_DATA: + return state + .set('loadedRowCount', 0) + .set('loadingRowCount', 0) + .set('loading', false) + .set('loaded', false) + default: return state } @@ -80,6 +100,18 @@ export const loadedSelector = createSelector( stateSelector, (state) => state.loaded ) +export const loadedRowCount = createSelector( + stateSelector, + (state) => state.loadedRowCount +) +export const loadingRowCount = createSelector( + stateSelector, + (state) => state.loadingRowCount +) +export const loadedRowsMap = createSelector( + stateSelector, + (state) => state.loadedRowsMap +) export const eventListSelector = createSelector(entitiesSelector, (entities) => entities.valueSeq().toArray() ) @@ -103,6 +135,14 @@ export function fetchAllEvents() { type: FETCH_ALL_REQUEST } } +export function fetchEventsWithPagination( + { startAt, endAt } = { startAt: 0, endAt: 10 } +) { + return { + type: FETCH_PAGINATION_EVENTS_REQUEST, + payload: { startAt, endAt } + } +} export function toggleSelection(uid) { return { @@ -111,6 +151,19 @@ export function toggleSelection(uid) { } } +export function changeLoadingInfo(data) { + return { + type: CHANGE_LOADING_INFO, + payload: { ...data } + } +} + +export function clearData() { + return { + type: CLEAR_DATA + } +} + /** * Sagas * */ @@ -129,7 +182,30 @@ export function* fetchAllSaga() { payload: snapshot.val() }) } +export function* fetchWithPaginationSaga({ payload }) { + const { startAt, endAt } = payload + console.log('TEST', startAt, endAt) + const ref = firebase.database().ref('events') + // .orderByChild('key') + // .startAt(startAt) + // .endAt(endAt) + + yield put({ + type: FETCH_ALL_START + }) + + const snapshot = yield call([ref, ref.once], 'value') + console.log('snapshot', snapshot) + + yield put({ + type: FETCH_ALL_SUCCESS, + payload: snapshot.val() + }) +} export function* saga() { - yield all([takeEvery(FETCH_ALL_REQUEST, fetchAllSaga)]) + yield all([ + takeEvery(FETCH_ALL_REQUEST, fetchAllSaga), + takeEvery(FETCH_PAGINATION_EVENTS_REQUEST, fetchWithPaginationSaga) + ]) } From 5a3a3ab074d97871336b475103463b01a789d6a6 Mon Sep 17 00:00:00 2001 From: User Date: Sun, 27 May 2018 22:20:46 +0300 Subject: [PATCH 5/5] refactoring --- .../events-table-virtualized-lazy-loading.js | 35 ++++++++++--------- admin/src/ducks/events.js | 10 +++--- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/admin/src/components/events/events-table-virtualized-lazy-loading.js b/admin/src/components/events/events-table-virtualized-lazy-loading.js index 204065e..d5ff516 100644 --- a/admin/src/components/events/events-table-virtualized-lazy-loading.js +++ b/admin/src/components/events/events-table-virtualized-lazy-loading.js @@ -38,7 +38,7 @@ export class EventsTable extends Component { } componentWillUnmount() { - Object.keys(this._timeoutIdMap).forEach((timeoutId) => { + Object.keys(this.timeoutIdMap).forEach((timeoutId) => { clearTimeout(timeoutId) }) } @@ -70,8 +70,8 @@ export class EventsTable extends Component { ) : ( {({ onRowsRendered, registerChild }) => ( @@ -83,7 +83,7 @@ export class EventsTable extends Component { onRowsRendered={onRowsRendered} rowCount={events.length} rowHeight={rowHeight} - rowRenderer={this._rowRenderer} + rowRenderer={this.rowRenderer} width={width} /> )} @@ -99,14 +99,14 @@ export class EventsTable extends Component { this.props.handleSelect(event.uid) } - _timeoutIdMap = () => {} + timeoutIdMap = () => {} - _isRowLoaded = ({ index }) => { + isRowLoaded = ({ index }) => { const { loadedRowsMap } = this.props return !!loadedRowsMap[index] // STATUS_LOADING or STATUS_LOADED } - _loadMoreRows = ({ startIndex, stopIndex }) => { + loadMoreRows = ({ startIndex, stopIndex }) => { const { loadedRowsMap, loadingRowCount } = this.props const increment = stopIndex - startIndex + 1 @@ -120,7 +120,7 @@ export class EventsTable extends Component { const timeoutId = setTimeout(() => { const { loadedRowCount, loadingRowCount } = this.props - delete this._timeoutIdMap[timeoutId] + delete this.timeoutIdMap[timeoutId] for (var i = startIndex; i <= stopIndex; i++) { loadedRowsMap[i] = STATUS_LOADED @@ -131,23 +131,24 @@ export class EventsTable extends Component { loadedRowCount: loadedRowCount + increment }) - console.log({ size: loadingRowCount, startIndex, stopIndex }) - - return promiseResolver() + promiseResolver({ + size: loadingRowCount, + startAt: startIndex, + endAt: stopIndex + }) }, 1000 + Math.round(Math.random() * 2000)) - this._timeoutIdMap[timeoutId] = true + this.timeoutIdMap[timeoutId] = true - let promiseResolver = () => { - this.props.fetchEventsWithPagination() - } + let promiseResolver return new Promise((resolve) => { - resolve(promiseResolver) + promiseResolver = (pagination) => + resolve(this.props.fetchEventsWithPagination(pagination)) }) } - _rowRenderer = ({ index, key, style }) => { + rowRenderer = ({ index, key, style }) => { const { events, loadedRowsMap } = this.props const row = events[index] diff --git a/admin/src/ducks/events.js b/admin/src/ducks/events.js index dc0012d..d5eb771 100644 --- a/admin/src/ducks/events.js +++ b/admin/src/ducks/events.js @@ -184,9 +184,11 @@ export function* fetchAllSaga() { } export function* fetchWithPaginationSaga({ payload }) { const { startAt, endAt } = payload - console.log('TEST', startAt, endAt) - const ref = firebase.database().ref('events') - // .orderByChild('key') + console.log('pagination', startAt, endAt) + const ref = firebase + .database() + .ref('events') + .orderByKey() // .startAt(startAt) // .endAt(endAt) @@ -195,7 +197,7 @@ export function* fetchWithPaginationSaga({ payload }) { }) const snapshot = yield call([ref, ref.once], 'value') - console.log('snapshot', snapshot) + console.log('snapshot', snapshot.val()) yield put({ type: FETCH_ALL_SUCCESS,