Skip to content

Commit adcb1b2

Browse files
authored
Merge pull request #930 from mapswipe/feature/group-contributor-export
Fix export for group contributor over 500 users
2 parents adfc2d8 + b939978 commit adcb1b2

File tree

20 files changed

+1000
-469
lines changed

20 files changed

+1000
-469
lines changed

.github/workflows/actions.yml

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,34 @@ jobs:
1212

1313
steps:
1414
- uses: actions/checkout@v2
15+
1516
- name: Set up Python ${{ matrix.python-version }}
1617
uses: actions/setup-python@v2
1718
with:
1819
python-version: ${{ matrix.python-version }}
20+
1921
- name: Install dependencies
2022
working-directory: ./mapswipe_workers
2123
run: |
2224
python -m pip install --upgrade pip
2325
pip install flake8 black==22.3.0 isort
26+
2427
- name: Code style
2528
working-directory: ./mapswipe_workers
2629
run: |
2730
black --check mapswipe_workers ../django
2831
flake8 --count --config setup.cfg mapswipe_workers/ ../django/
2932
isort --check --settings-file setup.cfg mapswipe_workers/ ../django/
33+
3034
- name: Assert check
3135
run: |
3236
cmp --silent ./postgres/initdb.sql ./mapswipe_workers/tests/integration/set_up_db.sql || {
3337
echo 'The set_up_db.sql is not same as initdb.sql. Please sync this files and push';
3438
diff ./postgres/initdb.sql ./mapswipe_workers/tests/integration/set_up_db.sql;
3539
exit 1;
3640
}
37-
- name: Setup Postgres Database Container
41+
42+
- name: Setup Postgres Database Container
3843
env:
3944
POSTGRES_PASSWORD: postgres
4045
POSTGRES_USER: postgres
@@ -44,12 +49,14 @@ jobs:
4449
touch postgres/serviceAccountKey.json
4550
docker-compose up --build --detach postgres
4651
for i in {1..5}; do docker-compose exec -T postgres pg_isready && s=0 && break || s=$? && sleep 5; done; (docker-compose logs postgres && exit $s)
52+
4753
- name: Deploy Firebase Rules and Functions
4854
env:
4955
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
5056
FIREBASE_DB: ${{ secrets.FIREBASE_DB }}
5157
run: |
5258
docker-compose run --rm firebase_deploy sh -c "firebase use $FIREBASE_DB && firebase deploy --token $FIREBASE_TOKEN --only database"
59+
5360
- name: Decrypt Service Account Key File
5461
working-directory: ./
5562
run: |
@@ -58,6 +65,7 @@ jobs:
5865
OPENSSL_PASSPHRASE: ${{ secrets.OPENSSL_PASSPHRASE }}
5966
OPENSSL_KEY: ${{ secrets.OPENSSL_KEY }}
6067
OPENSSL_IV: ${{ secrets.OPENSSL_IV }}
68+
6169
- name: Run Tests
6270
working-directory: ./mapswipe_workers
6371
env:
@@ -73,3 +81,27 @@ jobs:
7381
docker-compose run --rm mapswipe_workers_creation python -m unittest discover --verbose --start-directory tests/unittests/
7482
docker-compose run --rm mapswipe_workers_creation bash -c 'pip install pytest && pytest -ra -v --durations=10 tests/integration/'
7583
docker-compose run --rm django pytest -ra -v --durations=10
84+
85+
- name: Django Graphql Schema Check
86+
env:
87+
SOURCE_SCHEMA: './django/schema.graphql'
88+
LATEST_SCHEMA: './django-data/schema-latest.graphql'
89+
run: |
90+
docker-compose run --rm django bash -c 'wait-for-it postgres:5432 && ./manage.py graphql_schema --out /django-data/schema-latest.graphql' &&
91+
cmp --silent $SOURCE_SCHEMA $LATEST_SCHEMA || {
92+
echo 'The schema.graphql is not up to date with the latest changes. Please update and push latest';
93+
diff $SOURCE_SCHEMA $LATEST_SCHEMA;
94+
exit 1;
95+
}
96+
97+
- name: Django Database Migration Check
98+
env:
99+
POSTGRES_PASSWORD: postgres
100+
POSTGRES_USER: postgres
101+
POSTGRES_DB: postgres
102+
DJANGO_SECRET_KEY: test-django-secret-key
103+
run: |
104+
docker-compose run --rm django bash -c 'wait-for-it postgres:5432 && ./manage.py makemigrations --check --dry-run' || {
105+
echo 'There are some changes to be reflected in the migration. Make sure to run makemigrations';
106+
exit 1;
107+
}

community-dashboard/app/Base/configs/apollo.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const GRAPHQL_ENDPOINT = process.env.REACT_APP_GRAPHQL_ENDPOINT as string;
44

55
const link = new HttpLink({
66
uri: GRAPHQL_ENDPOINT,
7-
credentials: 'include',
7+
credentials: 'omit',
88
}) as unknown as ApolloLinkFromClient;
99

1010
/*
@@ -39,8 +39,9 @@ const apolloOptions: ApolloClientOptions<NormalizedCacheObject> = {
3939
errorPolicy: 'all',
4040
},
4141
watchQuery: {
42-
fetchPolicy: 'cache-and-network',
43-
nextFetchPolicy: 'cache-and-network',
42+
// NOTE: setting nextFetchPolicy to cache-and-network is risky
43+
fetchPolicy: 'network-only',
44+
nextFetchPolicy: 'cache-only',
4445
errorPolicy: 'all',
4546
},
4647
},

community-dashboard/app/components/ItemSelectInput/index.tsx

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,33 @@ export type SearchItemType = {
3030
isArchived?: boolean,
3131
};
3232

33+
const LIMIT = 5;
34+
3335
const USERS = gql`
34-
query UserOptions($search: String) {
35-
users(filters: { search: $search }, pagination: { limit: 5, offset: 0 }) {
36+
query UserOptions($search: String, $offset: Int!, $limit: Int!) {
37+
users(filters: { search: $search }, pagination: { limit: $limit, offset: $offset }) {
3638
items {
3739
userId
3840
username
3941
}
4042
count
43+
offset
44+
limit
4145
}
4246
}
4347
`;
4448

4549
const USER_GROUPS = gql`
46-
query UserGroupOptions($search: String) {
47-
userGroups(filters: { search: $search }, pagination: { limit: 5, offset: 0 }) {
50+
query UserGroupOptions($search: String, $offset: Int!, $limit: Int!) {
51+
userGroups(filters: { search: $search }, pagination: { limit: $limit, offset: $offset }) {
4852
items {
4953
isArchived
5054
userGroupId
5155
name
5256
}
5357
count
58+
offset
59+
limit
5460
}
5561
}
5662
`;
@@ -135,17 +141,19 @@ function ItemSelectInput<Name extends string>(props: ItemSelectInputProps<Name>)
135141

136142
const [opened, setOpened] = useState(false);
137143
const [searchText, setSearchText] = useState<string>('');
138-
139144
const debouncedSearchText = useDebouncedValue(searchText);
140145

141146
const variables = useMemo(() => ({
142147
search: debouncedSearchText,
148+
offset: 0,
149+
limit: LIMIT,
143150
}), [debouncedSearchText]);
144151

145152
const {
146153
previousData: previousUserData,
147154
data: userData = previousUserData,
148155
loading: userDataLoading,
156+
fetchMore: fetchMoreUser,
149157
} = useQuery<UserOptionsQuery, UserOptionsQueryVariables>(
150158
USERS,
151159
{
@@ -158,6 +166,7 @@ function ItemSelectInput<Name extends string>(props: ItemSelectInputProps<Name>)
158166
previousData: previousUserGroupData,
159167
data: userGroupData = previousUserGroupData,
160168
loading: userGroupDataLoading,
169+
fetchMore: fetchMoreUserGroup,
161170
} = useQuery<UserGroupOptionsQuery, UserGroupOptionsQueryVariables>(
162171
USER_GROUPS,
163172
{
@@ -168,8 +177,14 @@ function ItemSelectInput<Name extends string>(props: ItemSelectInputProps<Name>)
168177

169178
const loading = userDataLoading || userGroupDataLoading;
170179
const count = (userData?.users.count ?? 0) + (userGroupData?.userGroups.count ?? 0);
171-
const usersData = userData?.users.items;
172-
const userGroupsData = userGroupData?.userGroups.items;
180+
const usersData = useMemo(
181+
() => userData?.users.items,
182+
[userData?.users.items],
183+
);
184+
const userGroupsData = useMemo(
185+
() => userGroupData?.userGroups.items,
186+
[userGroupData?.userGroups.items],
187+
);
173188

174189
const data: SearchItemType[] = useMemo(
175190
() => ([
@@ -198,7 +213,6 @@ function ItemSelectInput<Name extends string>(props: ItemSelectInputProps<Name>)
198213

199214
const optionRendererParams = useCallback(
200215
(_: number | string, option: SearchItemType) => {
201-
// const isActive = key === selectedItem;
202216
const isActive = false;
203217

204218
return {
@@ -209,32 +223,86 @@ function ItemSelectInput<Name extends string>(props: ItemSelectInputProps<Name>)
209223
};
210224
},
211225
[],
212-
// [selectedItem],
226+
);
227+
228+
const handleShowMoreClick = useCallback(
229+
() => {
230+
fetchMoreUser({
231+
variables: {
232+
offset: (userData?.users.offset ?? 0) + LIMIT,
233+
},
234+
updateQuery: (previousResult, { fetchMoreResult }) => {
235+
const oldUsers = previousResult;
236+
const newUsers = fetchMoreResult;
237+
238+
if (!newUsers) {
239+
return previousResult;
240+
}
241+
242+
return ({
243+
users: {
244+
...newUsers.users,
245+
items: [
246+
...oldUsers.users?.items ?? [],
247+
...newUsers.users?.items ?? [],
248+
],
249+
},
250+
});
251+
},
252+
});
253+
fetchMoreUserGroup({
254+
variables: {
255+
offset: (userGroupData?.userGroups.offset ?? 0) + LIMIT,
256+
},
257+
updateQuery: (previousResult, { fetchMoreResult }) => {
258+
const oldUserGroups = previousResult;
259+
const newUserGroups = fetchMoreResult;
260+
261+
if (!newUserGroups) {
262+
return previousResult;
263+
}
264+
265+
return ({
266+
userGroups: {
267+
...newUserGroups.userGroups,
268+
items: [
269+
...oldUserGroups.userGroups.items ?? [],
270+
...newUserGroups.userGroups.items ?? [],
271+
],
272+
},
273+
});
274+
},
275+
});
276+
}, [
277+
variables,
278+
fetchMoreUser,
279+
fetchMoreUserGroup,
280+
userData?.users.offset,
281+
userGroupData?.userGroups.offset,
282+
],
213283
);
214284

215285
return (
216286
<SearchSelectInput
217287
{...otherProps}
288+
className={className}
218289
name="item-select-input"
219290
icons={(
220291
<IoSearch />
221292
)}
222293
optionRendererParams={optionRendererParams}
223294
optionRenderer={Option}
224295
options={[]}
225-
// onOptionsChange={setItemOptions}
226-
// value={selectedItem}
227296
value={undefined}
228297
onChange={handleSelectItem}
229-
// Other props
230-
className={className}
231298
keySelector={keySelector}
232299
labelSelector={titleSelector}
233300
onSearchValueChange={setSearchText}
234301
onShowDropdownChange={setOpened}
235302
searchOptions={data}
236303
optionsPending={loading}
237304
totalOptionsCount={count}
305+
handleShowMoreClick={handleShowMoreClick}
238306
/>
239307
);
240308
}

community-dashboard/app/components/SelectInput/SearchSelectInput.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,17 @@ export type SearchSelectInputProps<
8484
| 'optionRendererParams'
8585
>
8686
) & (
87-
{ nonClearable: true; onChange: (newValue: T, name: K) => void }
88-
| { nonClearable?: false; onChange: (newValue: T | undefined, name: K) => void }
89-
);
87+
{
88+
nonClearable: true;
89+
onChange: (newValue: T, name: K) => void
90+
}
91+
| {
92+
nonClearable?: false;
93+
onChange: (newValue: T | undefined, name: K) => void
94+
}
95+
) & {
96+
handleShowMoreClick?: () => void;
97+
};
9098

9199
const emptyList: unknown[] = [];
92100

@@ -113,6 +121,7 @@ function SearchSelectInput<
113121
onShowDropdownChange,
114122
optionRendererParams,
115123
optionRenderer,
124+
handleShowMoreClick,
116125
...otherProps
117126
} = props;
118127

@@ -288,6 +297,7 @@ function SearchSelectInput<
288297
onFocusedKeyChange={setFocusedKey}
289298
hasValue={isDefined(value)}
290299
persistentOptionPopup={false}
300+
handleShowMoreClick={handleShowMoreClick}
291301
/>
292302
);
293303
}

community-dashboard/app/components/SelectInputContainer/EmptyOptions/index.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import React from 'react';
2+
import { IoChevronForward } from 'react-icons/io5';
3+
import Button from '#components/Button';
24

35
import styles from './styles.css';
46

@@ -9,6 +11,7 @@ interface DefaultEmptyComponentProps {
911
totalOptionsCount: number | undefined;
1012
emptyMessage?: React.ReactNode;
1113
emptyFilteredMessage?: React.ReactNode;
14+
handleShowMoreClick?: () => void;
1215
}
1316

1417
function EmptyOptions(props: DefaultEmptyComponentProps) {
@@ -19,6 +22,7 @@ function EmptyOptions(props: DefaultEmptyComponentProps) {
1922
totalOptionsCount = 0,
2023
emptyMessage = 'No options available',
2124
emptyFilteredMessage = 'No matching options available',
25+
handleShowMoreClick,
2226
} = props;
2327

2428
if (pending) {
@@ -49,8 +53,21 @@ function EmptyOptions(props: DefaultEmptyComponentProps) {
4953
const hiddenOptions = totalOptionsCount - optionsCount;
5054
if (hiddenOptions > 0) {
5155
return (
52-
<div className={styles.empty}>
53-
{`and ${hiddenOptions} more`}
56+
<div className={styles.hiddenOptionsCount}>
57+
<span className={styles.hiddenCountMessage}>
58+
{`and ${hiddenOptions} more`}
59+
</span>
60+
{handleShowMoreClick && (
61+
<Button
62+
className={styles.button}
63+
name={undefined}
64+
onClick={handleShowMoreClick}
65+
actions={<IoChevronForward />}
66+
variant="transparent"
67+
>
68+
Show more
69+
</Button>
70+
)}
5471
</div>
5572
);
5673
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,20 @@
11
.empty {
22
padding: var(--spacing-small) var(--spacing-large);
3+
min-height: 2em;
34
color: var(--color-text-watermark);
45
}
6+
.hidden-options-count {
7+
display: flex;
8+
align-items: baseline;
9+
padding: var(--spacing-small) var(--spacing-large);
10+
}
11+
.hidden-count-message {
12+
display: flex;
13+
flex-grow: 1;
14+
justify-content: flex-start;
15+
color: var(--color-text-watermark);
16+
}
17+
.button {
18+
flex-shrink: 0;
19+
padding: 0;
20+
}

0 commit comments

Comments
 (0)