Skip to content

Commit 76d8e77

Browse files
committed
feat(groups): add virtual scrolling at groups list
Add environment variable `REACT_APP_GROUPS_PER_PAGE` (default 1000), which controls the `perPage` parameter of the API request being made to fetch groups list. Add virtual scrolling (via react-virtualized) at Groups page. Addresses topcoder-archive#155
1 parent eb8b033 commit 76d8e77

File tree

6 files changed

+127
-21
lines changed

6 files changed

+127
-21
lines changed

client/src/components/GroupsSideMenu/filters.js

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import React, { useState, useEffect } from "react";
2+
import { List } from "react-virtualized";
23
import PT from "prop-types";
4+
import _ from "lodash";
35

46
import SearchBox from "../searchBox";
57
import Button from "../Button";
@@ -133,23 +135,61 @@ function GroupsSection({
133135
selectedItemId,
134136
loadingGroups,
135137
}) {
138+
/**
139+
* Calculates the height value of an item.
140+
* @param {Number} index index of the item to be calculated
141+
* @return {Number} row height
142+
*/
143+
function rowHeight({ index }) {
144+
return items[index].name.length <= 30
145+
? 60
146+
: 60 + (Math.floor(items[index].name.length / 30) - 1) * 20 + 20;
147+
}
148+
149+
/**
150+
* Row renderer for react-virtualized#List.
151+
* Renders each item as a row.
152+
* @param {String} key unique key for the item
153+
* @param {Number} index index of the item (row)
154+
* @param {Object} style
155+
* @return {SectionRow} row element
156+
*/
157+
function rowRenderer({ key, index, style }) {
158+
return (
159+
<div key={key} style={style}>
160+
<SectionRow
161+
key={`${title}${index}`}
162+
title={items[index].name}
163+
badge={items[index].count + ""}
164+
action={(isSelected) =>
165+
!isSelected && onItemClicked && onItemClicked(title, items[index])
166+
}
167+
selected={items[index].id === selectedItemId}
168+
/>
169+
</div>
170+
);
171+
}
172+
136173
return (
137174
<>
138175
<div className={styles.sectionTitle}>{title}</div>
139176
<div className={styles.sectionItemsContainer}>
140-
{items.map((item, index) => {
141-
return (
142-
<SectionRow
143-
key={`${title}${index}`}
144-
title={item.name}
145-
badge={item.count + ""}
146-
action={(isSelected) =>
147-
!isSelected && onItemClicked && onItemClicked(title, item)
148-
}
149-
selected={item.id === selectedItemId}
150-
/>
151-
);
152-
})}
177+
<List
178+
className={styles.groupsList}
179+
width={385}
180+
height={
181+
items.length > 7
182+
? 450
183+
: _.reduce(
184+
_.map(items, (item, index) => rowHeight({ index })),
185+
(a, b) => a + b,
186+
0
187+
)
188+
}
189+
rowCount={items.length}
190+
rowHeight={rowHeight}
191+
rowRenderer={rowRenderer}
192+
/>
153193
</div>
154194
{items.length === 0 && !loadingGroups && (
155195
<div className={styles.message}>No results found</div>

client/src/components/GroupsSideMenu/filters.module.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@
7373
flex-direction: column;
7474
}
7575

76+
.groupsList {
77+
outline: 0;
78+
}
79+
7680
.sectionItemsContainer > div {
7781
margin-top: 20px;
7882
}
@@ -101,6 +105,7 @@
101105
}
102106

103107
.sectionItemTitle {
108+
width: 280px;
104109
color: gray2;
105110
word-break: break-all;
106111
padding-right: 12px;

client/src/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export default {
66
API_URL: process.env.REACT_APP_API_URL,
77
API_PREFIX: process.env.REACT_APP_API_PREFIX,
88
GROUPS_API_URL: process.env.REACT_APP_GROUPS_API_URL,
9+
GROUPS_PER_PAGE: process.env.REACT_APP_GROUPS_PER_PAGE || 1000,
910

1011
BULK_UPLOAD_TEMPLATE_ID: process.env.REACT_APP_BULK_UPLOAD_TEMPLATE_ID,
1112
EMSI_SKILLPROVIDER_ID: process.env.REACT_APP_EMSI_SKILLPROVIDER_ID,

client/src/lib/groups.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export async function getGroups(apiClient, handle, cancelToken) {
3939
// Now, get my groups first
4040
try {
4141
response = await apiClient.get(
42-
`${config.GROUPS_API_URL}?universalUID=${userId}&membershipType=user`,
42+
`${config.GROUPS_API_URL}?universalUID=${userId}&membershipType=user&perPage=${config.GROUPS_PER_PAGE}`,
4343
{ cancelToken }
4444
);
4545
} catch (error) {
@@ -66,7 +66,7 @@ export async function getGroups(apiClient, handle, cancelToken) {
6666
// Fetch all groups in the org
6767
try {
6868
response = await apiClient.get(
69-
`${config.GROUPS_API_URL}?organizationId=${organizationId}`,
69+
`${config.GROUPS_API_URL}?organizationId=${organizationId}&perPage=${config.GROUPS_PER_PAGE}`,
7070
{ cancelToken }
7171
);
7272
} catch (error) {

package-lock.json

Lines changed: 65 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"lodash": "^4.17.19",
3838
"multer": "^1.4.2",
3939
"node-cache": "^5.1.0",
40+
"react-virtualized": "^9.21.2",
4041
"swagger-ui-express": "^4.1.4",
4142
"tc-bus-api-wrapper": "topcoder-platform/tc-bus-api-wrapper.git#feature/auth0-proxy-server",
4243
"tc-core-library-js": "github:appirio-tech/tc-core-library-js#v2.6.4",

0 commit comments

Comments
 (0)