Skip to content

Commit a0047b2

Browse files
output from challenge 30115867
1 parent 8c70aae commit a0047b2

File tree

7 files changed

+471
-1
lines changed

7 files changed

+471
-1
lines changed

src/actions/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import reviewOpportunityActions from './reviewOpportunity';
1313
import lookupActions from './lookup';
1414
import settingsActions from './settings';
1515
import lookerActions from './looker';
16+
import memberSearchActions from './member-search';
1617

1718
export const actions = {
1819
auth: authActions.auth,
@@ -30,6 +31,7 @@ export const actions = {
3031
lookup: lookupActions.lookup,
3132
settings: settingsActions.settings,
3233
looker: lookerActions.looker,
34+
memberSearch: memberSearchActions.memberSearch,
3335
};
3436

3537
export default undefined;

src/actions/member-search.js

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* @module "actions.member-search"
3+
* @desc Actions for management of members search.
4+
*/
5+
import _ from 'lodash'
6+
import { createActions } from 'redux-actions';
7+
import { getService } from '../services/member-search';
8+
9+
/**
10+
* @desc Creates an action that fetchs the members data for a search term, and
11+
* adds result to the store cumulatively.
12+
* @param {String} searchTerm the search term
13+
* @param {Number} offset the number of records to skip
14+
* @param {Number} limit the maximum number of the return results
15+
* @return {Action}
16+
*/
17+
function loadMemberSearch(searchTerm, offset = 0, limit = 10) {
18+
return getService().getUsernameMatches(searchTerm, offset, limit);
19+
}
20+
21+
/**
22+
* @static
23+
* @desc Creates an action that fetchs the members data for a search tag, and
24+
* adds result to the store.
25+
* @param {Object} tag the tag
26+
* @return {Action}
27+
*/
28+
function loadMemberSearchForTag(tag) {
29+
return getService().getTopMembers(tag);
30+
}
31+
32+
/**
33+
* @static
34+
* @desc Creates an action that check if the term is a tag name. If it is unable to check,
35+
* or invalid data returned then resets the members data and search terms data in the store
36+
* to intial values.
37+
* @param {String} searchTerm the search term
38+
* @return {Action}
39+
*/
40+
function checkIfSearchTermIsATag(searchTerm) {
41+
return getService().checkIfSearchTermIsATag(searchTerm);
42+
}
43+
44+
/**
45+
* @static
46+
* @desc Creates an action that saves the current search term.
47+
* @param {String} searchTerm the search term
48+
* @return {Action}
49+
*/
50+
function setSearchTerm(searchTerm) {
51+
return {
52+
previousSearchTerm: searchTerm
53+
}
54+
}
55+
56+
/**
57+
* @static
58+
* @desc Creates an action that saves the current search tag.
59+
* @param {Object} searchTag the search tag
60+
* @return {Action}
61+
*/
62+
function setSearchTag(searchTag) {
63+
return {
64+
searchTermTag: searchTag
65+
}
66+
}
67+
68+
export default createActions({
69+
MEMBER_SEARCH: {
70+
USERNAME_SEARCH_SUCCESS: loadMemberSearch,
71+
CHECK_IF_SEARCH_TERM_IS_A_TAG: checkIfSearchTermIsATag,
72+
TOP_MEMBER_SEARCH_SUCCESS: loadMemberSearchForTag,
73+
CLEAR_MEMBER_SEARCH: _.noop,
74+
LOAD_MORE_USERNAMES: _.noop,
75+
MEMBER_SEARCH_SUCCESS: _.noop,
76+
SET_SEARCH_TERM: setSearchTerm,
77+
SET_SEARCH_TAG: setSearchTag,
78+
RESET_SEARCH_TERM: _.noop
79+
}
80+
});

src/reducers/index.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import settings, { factory as settingsFactory }
2222
from './settings';
2323
import looker, { factory as lookerFactory }
2424
from './looker';
25-
25+
import memberSearch, { factory as memberSearchFactory } from './member-search';
2626

2727
export function factory(options) {
2828
return redux.resolveReducers({
@@ -41,6 +41,7 @@ export function factory(options) {
4141
mySubmissionsManagement: mySubmissionsManagementFactory(options),
4242
settings: settingsFactory(options),
4343
looker: lookerFactory(options),
44+
memberSearch: memberSearchFactory(options),
4445
});
4546
}
4647

@@ -60,4 +61,5 @@ export default ({
6061
mySubmissionsManagement,
6162
settings,
6263
looker,
64+
memberSearch,
6365
});

src/reducers/member-search.js

+245
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
/**
2+
* @module "reducers.member-search"
3+
* @desc Reducer for {@link module:actions.member-search} actions.
4+
*
5+
* State segment managed by this reducer has the following structure:
6+
* @param {Boolean} pageLoaded `true` if loading members data for a search term is done
7+
* `false`if starting loading members data for a search term,
8+
* or loading failed
9+
* @param {Boolean} loadingMore `true` if request for loading more data is in progress;
10+
* otherwise `false`
11+
* @param {Boolean} error `true` if failed to load member data; otherwise `false`
12+
* @param {Number} totalCount the number of matched members for a search term
13+
* @param {Boolean} moreMatchesAvailable `true` if there are more matched members, for
14+
* a search term, to load; otherwise `false`
15+
* @param {Array<{}>} usernameMatches contains loaded members data for a search term
16+
* @param {Array<{}>} topMembers contains loaded members data for a search tag
17+
* @param {String} previousSearchTerm the current search term
18+
* @param {Object} searchTermTag the current search tag data if the search term is a tag name;
19+
* otherwise `null`
20+
*/
21+
import _ from 'lodash';
22+
import { redux } from 'topcoder-react-utils';
23+
import actions from '../actions/member-search';
24+
import { fireErrorMessage } from '../utils/errors';
25+
26+
/**
27+
* @private
28+
* Returns the new state with the intial members data.
29+
*/
30+
function memberSearchFailure(state) {
31+
return Object.assign({}, state, {
32+
loadingMore: false,
33+
error: true,
34+
totalCount: 0,
35+
usernameMatches: [],
36+
topMembers: [],
37+
});
38+
}
39+
40+
/**
41+
* @private
42+
* Returns the new state with the intial search terms data.
43+
*/
44+
function resetSearchTerm(state) {
45+
return Object.assign({}, state, {
46+
pageLoaded: false,
47+
previousSearchTerm: null,
48+
searchTermTag: null,
49+
});
50+
}
51+
52+
/**
53+
* @private
54+
* Returns the new state with the intial members and search terms data.
55+
*/
56+
function memberSearchFailureAndResetSearchTerm(state) {
57+
let newState = state;
58+
newState = memberSearchFailure(newState);
59+
newState = resetSearchTerm(newState);
60+
return newState;
61+
}
62+
63+
/**
64+
* Handles the actual results of loading members data for a search term cumulatively,
65+
* and clear members data on request failure.
66+
* @param {Object} state
67+
* @param {Object} action
68+
* @return {Object} New state.
69+
*/
70+
function onUsernameSearchSuccess(state, action) {
71+
const { payload } = action;
72+
if (action.error) {
73+
fireErrorMessage('Could not fetch username matches', '');
74+
return memberSearchFailure(state);
75+
}
76+
77+
return Object.assign({}, state, {
78+
loadingMore: false,
79+
totalCount: payload.totalCount,
80+
moreMatchesAvailable: state.usernameMatches.length + payload.usernameMatches.length
81+
< payload.totalCount,
82+
usernameMatches: state.usernameMatches.concat(payload.usernameMatches),
83+
});
84+
}
85+
86+
/**
87+
* Clear members data and search terms data on request failure of checking if the search term is
88+
* a tag name.
89+
* @param {Object} state
90+
* @param {Object} action
91+
* @return {Object} New state if error; otherwise the same state.
92+
*/
93+
function onCheckIfSearchTermIsATag(state, action) {
94+
if (action.error) {
95+
fireErrorMessage('Could not determine if search term is a tag', '');
96+
return memberSearchFailureAndResetSearchTerm(state);
97+
}
98+
99+
return state;
100+
}
101+
102+
/**
103+
* Handles the actual results of loading members data for a search tag, and
104+
* clear members data and search terms data on request failure.
105+
* @param {Object} state
106+
* @param {Object} action
107+
* @return {Object} New state.
108+
*/
109+
function onTopMemberSearchSuccess(state, action) {
110+
const { payload } = action;
111+
if (action.error) {
112+
fireErrorMessage('Could not fetch top members', '');
113+
return memberSearchFailureAndResetSearchTerm(state);
114+
}
115+
116+
return Object.assign({}, state, {
117+
topMembers: payload.topMembers,
118+
});
119+
}
120+
121+
/**
122+
* Clear members data to the intial state.
123+
* @param {Object} state
124+
* @param {Object} action
125+
* @return {Object} New state.
126+
*/
127+
function onClearMemberSearch(state) {
128+
return Object.assign({}, state, {
129+
pageLoaded: false,
130+
loadingMore: false,
131+
error: false,
132+
totalCount: 0,
133+
usernameMatches: [],
134+
topMembers: [],
135+
});
136+
}
137+
138+
/**
139+
* Marks the request of loading more members data for a search term as in progress
140+
* @param {Object} state
141+
* @param {Object} action
142+
* @return {Object} New state.
143+
*/
144+
function onLoadMoreUsernames(state) {
145+
return Object.assign({}, state, {
146+
loadingMore: true,
147+
});
148+
}
149+
150+
/**
151+
* Marks the loaded members data for a search term or search tag (if any) as ready.
152+
* @param {Object} state
153+
* @param {Object} action
154+
* @return {Object} New state.
155+
*/
156+
function onMemberSearchSuccess(state) {
157+
return Object.assign({}, state, {
158+
pageLoaded: true,
159+
});
160+
}
161+
162+
/**
163+
* Handles setting the current search term.
164+
* @param {Object} state
165+
* @param {Object} action
166+
* @return {Object} New state.
167+
*/
168+
function onSetSearchTerm(state, action) {
169+
const { payload } = action;
170+
return Object.assign({}, state, {
171+
error: false,
172+
previousSearchTerm: payload.previousSearchTerm,
173+
});
174+
}
175+
176+
/**
177+
* Handles setting the current search tag.
178+
* @param {Object} state
179+
* @param {Object} action
180+
* @return {Object} New state.
181+
*/
182+
function onSetSearchTag(state, action) {
183+
const { payload } = action;
184+
return Object.assign({}, state, {
185+
searchTermTag: payload.searchTermTag,
186+
});
187+
}
188+
189+
/**
190+
* Handles clearing the current search term and search tag.
191+
* @param {Object} state
192+
* @param {Object} action
193+
* @return {Object} New state.
194+
*/
195+
function onResetSearchTerm(state) {
196+
return resetSearchTerm(state);
197+
}
198+
199+
/**
200+
* Creates a new member search reducer with the specified initial state.
201+
* @param {Object} initialState Optional. Initial state.
202+
* @return {Function} member search reducer.
203+
*/
204+
function create(initialState = {}) {
205+
const a = actions.memberSearch;
206+
return redux.handleActions({
207+
[a.usernameSearchSuccess]: onUsernameSearchSuccess,
208+
[a.checkIfSearchTermIsATag]: onCheckIfSearchTermIsATag,
209+
[a.topMemberSearchSuccess]: onTopMemberSearchSuccess,
210+
[a.clearMemberSearch]: onClearMemberSearch,
211+
[a.loadMoreUsernames]: onLoadMoreUsernames,
212+
[a.memberSearchSuccess]: onMemberSearchSuccess,
213+
[a.setSearchTerm]: onSetSearchTerm,
214+
[a.setSearchTag]: onSetSearchTag,
215+
[a.resetSearchTerm]: onResetSearchTerm,
216+
}, _.defaults(initialState, {
217+
pageLoaded: false,
218+
loadingMore: false,
219+
error: false,
220+
totalCount: 0,
221+
moreMatchesAvailable: false,
222+
usernameMatches: [],
223+
topMembers: [],
224+
previousSearchTerm: null,
225+
searchTermTag: null,
226+
}));
227+
}
228+
229+
/**
230+
* Factory which creates a new reducer with its initial state tailored to the
231+
* given options object, if specified (for server-side rendering). If options
232+
* object is not specified, it creates just the default reducer. Accepted options are:
233+
* @return {Promise}
234+
* @resolves {Function(state, action): state} New reducer.
235+
*/
236+
export function factory() {
237+
return Promise.resolve(create());
238+
}
239+
240+
/**
241+
* @static
242+
* @member default
243+
* @desc Reducer with default initial state.
244+
*/
245+
export default create();

src/services/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import * as user from './user';
1515
import * as lookup from './lookup';
1616
import * as userTraits from './user-traits';
1717
import * as submissions from './submissions';
18+
import * as memberSearch from './member-search';
1819

1920
export const services = {
2021
api,
@@ -31,6 +32,7 @@ export const services = {
3132
lookup,
3233
userTraits,
3334
submissions,
35+
memberSearch,
3436
};
3537

3638
export default undefined;

0 commit comments

Comments
 (0)