Skip to content

Commit 2106478

Browse files
committedApr 2, 2020
merge with develop branch
2 parents 84cb027 + ea288ed commit 2106478

34 files changed

+9832
-11159
lines changed
 

‎.circleci/config.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ jobs:
2828
- attach_workspace:
2929
at: .
3030
- run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
31-
- run: npm publish --tag test-release
32-
31+
- run: npm publish
32+
# dont change anything
3333
workflows:
3434
version: 2
3535
build:

‎README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ The [Topcoder](https://www.topcoder.com) lib for internal ReactJS projects.
1010

1111
Change the property in `AV_SCAN_SCORER_REVIEW_TYPE_ID` in config.
1212

13+
### Configuration for AV-Scan scorer review type ID
14+
15+
Change the property in `PROVISIONAL_SCORING_COMPLETED_REVIEW_TYPE_ID` in config.
16+
1317
### Development
1418
```shell
1519
# Install dependencies
@@ -19,7 +23,7 @@ npm install
1923
npm test
2024
npm run build
2125

22-
# Go to other project which depends on the topcoder-react-lib, config its package.json so
26+
# Go to other project which depends on the topcoder-react-lib, config its package.json so
2327
# that the 'topcoder-react-lib' points to the local foler path of topcoder-react-lib:
2428
#
2529
# "dependencies": {

‎__tests__/__snapshots__/index.js.snap

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,24 @@ Object {
6868
"getModelsInit": [Function],
6969
"getOsesDone": [Function],
7070
"getOsesInit": [Function],
71+
"getReviewTypesDone": [Function],
72+
"getReviewTypesInit": [Function],
7173
"getSkillTagsDone": [Function],
7274
"getSkillTagsInit": [Function],
7375
"getTypesDone": [Function],
7476
"getTypesInit": [Function],
7577
},
78+
"memberSearch": Object {
79+
"checkIfSearchTermIsATag": [Function],
80+
"clearMemberSearch": [Function],
81+
"loadMoreUsernames": [Function],
82+
"memberSearchSuccess": [Function],
83+
"resetSearchTerm": [Function],
84+
"setSearchTag": [Function],
85+
"setSearchTerm": [Function],
86+
"topMemberSearchSuccess": [Function],
87+
"usernameSearchSuccess": [Function],
88+
},
7689
"memberTasks": Object {
7790
"dropAll": [Function],
7891
"getDone": [Function],
@@ -101,6 +114,18 @@ Object {
101114
"getUserSrmDone": [Function],
102115
"getUserSrmInit": [Function],
103116
},
117+
"notifications": Object {
118+
"dismissChallengeNotificationsDone": [Function],
119+
"dismissChallengeNotificationsInit": [Function],
120+
"getNotificationsDone": [Function],
121+
"getNotificationsInit": [Function],
122+
"markAllNotificationAsReadDone": [Function],
123+
"markAllNotificationAsReadInit": [Function],
124+
"markAllNotificationAsSeenDone": [Function],
125+
"markAllNotificationAsSeenInit": [Function],
126+
"markNotificationAsReadDone": [Function],
127+
"markNotificationAsReadInit": [Function],
128+
},
104129
"profile": Object {
105130
"addSkillDone": [Function],
106131
"addSkillInit": [Function],
@@ -246,9 +271,11 @@ Object {
246271
"groups": [Function],
247272
"looker": [Function],
248273
"lookup": [Function],
274+
"memberSearch": [Function],
249275
"memberTasks": [Function],
250276
"members": [Function],
251277
"mySubmissionsManagement": [Function],
278+
"notifications": [Function],
252279
"profile": [Function],
253280
"reviewOpportunity": [Function],
254281
"settings": [Function],
@@ -298,10 +325,18 @@ Object {
298325
"default": undefined,
299326
"getService": [Function],
300327
},
328+
"memberSearch": Object {
329+
"default": undefined,
330+
"getService": [Function],
331+
},
301332
"members": Object {
302333
"default": undefined,
303334
"getService": [Function],
304335
},
336+
"notifications": Object {
337+
"default": undefined,
338+
"getService": [Function],
339+
},
305340
"reviewOpportunities": Object {
306341
"default": undefined,
307342
"getReviewOpportunitiesService": [Function],

‎__tests__/actions/__snapshots__/lookup.js.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Object {
1313
"getModelsInit": [Function],
1414
"getOsesDone": [Function],
1515
"getOsesInit": [Function],
16+
"getReviewTypesDone": [Function],
17+
"getReviewTypesInit": [Function],
1618
"getSkillTagsDone": [Function],
1719
"getSkillTagsInit": [Function],
1820
"getTypesDone": [Function],

‎__tests__/reducers/__snapshots__/lookup.js.snap

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Object {
1818
"models": Array [],
1919
"osPage": 1,
2020
"oses": Array [],
21+
"reviewTypes": Array [],
2122
"skillTags": Array [
2223
Object {
2324
"domain": "SKILLS",
@@ -48,6 +49,7 @@ Object {
4849
"models": Array [],
4950
"osPage": 1,
5051
"oses": Array [],
52+
"reviewTypes": Array [],
5153
"skillTags": Array [
5254
Object {
5355
"domain": "SKILLS",
@@ -72,6 +74,7 @@ Object {
7274
"models": Array [],
7375
"osPage": 1,
7476
"oses": Array [],
77+
"reviewTypes": Array [],
7578
"skillTags": Array [
7679
Object {
7780
"domain": "SKILLS",
@@ -96,6 +99,7 @@ Object {
9699
"models": Array [],
97100
"osPage": 1,
98101
"oses": Array [],
102+
"reviewTypes": Array [],
99103
"skillTags": Array [
100104
Object {
101105
"domain": "SKILLS",
@@ -119,6 +123,7 @@ Object {
119123
"models": Array [],
120124
"osPage": 1,
121125
"oses": Array [],
126+
"reviewTypes": Array [],
122127
"skillTags": Array [],
123128
"types": Array [],
124129
}
@@ -142,6 +147,7 @@ Object {
142147
"models": Array [],
143148
"osPage": 1,
144149
"oses": Array [],
150+
"reviewTypes": Array [],
145151
"skillTags": Array [
146152
Object {
147153
"domain": "SKILLS",
@@ -172,6 +178,7 @@ Object {
172178
"models": Array [],
173179
"osPage": 1,
174180
"oses": Array [],
181+
"reviewTypes": Array [],
175182
"skillTags": Array [
176183
Object {
177184
"domain": "SKILLS",
@@ -196,6 +203,7 @@ Object {
196203
"models": Array [],
197204
"osPage": 1,
198205
"oses": Array [],
206+
"reviewTypes": Array [],
199207
"skillTags": Array [
200208
Object {
201209
"domain": "SKILLS",
@@ -220,6 +228,7 @@ Object {
220228
"models": Array [],
221229
"osPage": 1,
222230
"oses": Array [],
231+
"reviewTypes": Array [],
223232
"skillTags": Array [
224233
Object {
225234
"domain": "SKILLS",
@@ -243,6 +252,7 @@ Object {
243252
"models": Array [],
244253
"osPage": 1,
245254
"oses": Array [],
255+
"reviewTypes": Array [],
246256
"skillTags": Array [],
247257
"types": Array [],
248258
}

‎config/default.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"AV_SCAN_SCORER_REVIEW_TYPE_ID": "",
3+
"PROVISIONAL_SCORING_COMPLETED_REVIEW_TYPE_ID": "",
34
"PAGE_SIZE": 50,
45
"REVIEW_OPPORTUNITY_PAGE_SIZE": 1000
56
}

‎config/development.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
{
2-
"AV_SCAN_SCORER_REVIEW_TYPE_ID": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3"
2+
"AV_SCAN_SCORER_REVIEW_TYPE_ID": "68c5a381-c8ab-48af-92a7-7a869a4ee6c3",
3+
"PROVISIONAL_SCORING_COMPLETED_REVIEW_TYPE_ID": "52c91e85-745f-4e62-b592-9879a2dfe9fd"
34
}

‎config/production.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
{
2-
"AV_SCAN_SCORER_REVIEW_TYPE_ID": "55bbb17d-aac2-45a6-89c3-a8d102863d05"
2+
"AV_SCAN_SCORER_REVIEW_TYPE_ID": "55bbb17d-aac2-45a6-89c3-a8d102863d05",
3+
"PROVISIONAL_SCORING_COMPLETED_REVIEW_TYPE_ID": "df51ca7d-fb0a-4147-9569-992fcf5aae48"
34
}

‎config/test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,12 @@ module.exports = {
44
V3: 'https://api.topcoder-dev.com/v3',
55
},
66
dummyConfigKey: 'Dummy config value',
7+
SECRET: {
8+
TC_M2M: {
9+
AUTH0_URL: '',
10+
AUTH0_AUDIENCE: '',
11+
TOKEN_CACHE_TIME: '',
12+
AUTH0_PROXY_SERVER_URL: '',
13+
},
14+
},
715
};

‎config/webpack/default.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
const webpack = require('webpack');
33

44
module.exports = {
5-
plugins: [
6-
// eslint-disable-next-line global-require
7-
new webpack.DefinePlugin({ CONFIG: JSON.stringify(require('config')) }),
8-
],
5+
node: {
6+
tls: 'empty',
7+
fs: 'empty',
8+
net: 'empty',
9+
},
910
// Don't include the dependencies to keep built bundle small,
1011
// they will be provided by the app using this lib
1112
externals: [
@@ -24,8 +25,10 @@ module.exports = {
2425
'tc-accounts',
2526
'to-capital-case',
2627
'topcoder-react-utils',
28+
'tc-core-library-js',
29+
],
30+
plugins: [
31+
// eslint-disable-next-line global-require
32+
new webpack.DefinePlugin({ CONFIG: JSON.stringify(require('config')) }),
2733
],
28-
node: {
29-
fs: 'empty',
30-
},
3134
};

‎dist/dev/index.js

Lines changed: 0 additions & 1445 deletions
This file was deleted.

‎docs/actions.lookup.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Actions related to lookup data.
1111
* [.getCountriesDone()](#module_actions.lookup.getCountriesDone) ⇒ <code>Action</code>
1212
* [.getAllCountriesInit()](#module_actions.lookup.getCountriesInit) ⇒ <code>Action</code>
1313
* [.getAllCountriesDone(tokenV3)](#module_actions.lookup.getCountriesDone) ⇒ <code>Action</code>
14+
* [.getReviewTypesInit()](#module_actions.lookup.getReviewTypesInit) ⇒ <code>Action</code>
15+
* [.getReviewTypesDone(tokenV3)](#module_actions.lookup.ggetReviewTypesDone) ⇒ <code>Action</code>
1416

1517
<a name="module_actions.lookup.getSkillTagsInit"></a>
1618

@@ -40,3 +42,19 @@ Creates an action that gets all countries new version.
4042
| Param | Type | Description |
4143
| --- | --- | --- |
4244
| tokenV3 | <code>String</code> | Topcoder v3 auth token. |
45+
<a name="module_actions.lookup.getReviewTypesInit"></a>
46+
47+
### actions.lookup.getReviewTypesInit() ⇒ <code>Action</code>
48+
Creates an action that gets all reiview types.
49+
50+
**Kind**: static method of [<code>actions.lookup</code>](#module_actions.lookup)
51+
<a name="module_actions.lookup.getReviewTypesDone"></a>
52+
53+
### actions.lookup.getReviewTypesDone(tokenV3) ⇒ <code>Action</code>
54+
Creates an action that gets all review types.
55+
56+
**Kind**: static method of [<code>actions.lookup</code>](#module_actions.lookup)
57+
58+
| Param | Type | Description |
59+
| --- | --- | --- |
60+
| tokenV3 | <code>String</code> | Topcoder v3 auth token. |

‎docs/reducers.lookup.md

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,35 +18,48 @@ State segment managed by this reducer has the following structure:
1818
* _inner_
1919
* [~onGetSkillTagsDone(state, action)](#module_reducers.lookup..onGetSkillTagsDone) ⇒ <code>Object</code>
2020
* [~onGetCountriesDone(state, action)](#module_reducers.lookup..onGetCountriesDone) ⇒ <code>Object</code>
21+
* [~onGetReviewTypesDone(state, action)](#module_reducers.lookup..onGetReviewTypesDone) ⇒ <code>Object</code>
2122
* [~create(initialState)](#module_reducers.lookup..create) ⇒ <code>function</code>
2223

2324
<a name="module_reducers.lookup.default"></a>
2425

2526
### reducers.lookup.default
2627
Reducer with default initial state.
2728

28-
**Kind**: static property of [<code>reducers.lookup</code>](#module_reducers.lookup)
29+
**Kind**: static property of [<code>reducers.lookup</code>](#module_reducers.lookup)
2930
<a name="module_reducers.lookup.factory"></a>
3031

3132
### reducers.lookup.factory() ⇒ <code>Promise</code>
3233
Factory which creates a new reducer.
3334

34-
**Kind**: static method of [<code>reducers.lookup</code>](#module_reducers.lookup)
35-
**Resolves**: <code>Function(state, action): state</code> New reducer.
35+
**Kind**: static method of [<code>reducers.lookup</code>](#module_reducers.lookup)
36+
**Resolves**: <code>Function(state, action): state</code> New reducer.
3637
<a name="module_reducers.lookup..onGetSkillTagsDone"></a>
3738

3839
### reducers.lookup~onGetSkillTagsDone(state, action) ⇒ <code>Object</code>
3940
Handles LOOKUP/GET_SKILL_TAGS_DONE action.
4041

41-
**Kind**: static method of [<code>reducers.lookup</code>](#module_reducers.lookup)
42-
**Resolves**: <code>Function(state, action): state</code> New reducer.
42+
**Kind**: static method of [<code>reducers.lookup</code>](#module_reducers.lookup)
43+
**Resolves**: <code>Function(state, action): state</code> New reducer.
4344
<a name="module_reducers.lookup..onGetCountriesDone"></a>
4445

4546
### reducers.lookup~onGetCountriesDone(state, action) ⇒ <code>Object</code>
4647
Handles LOOKUP/GET_COUNTRIES_DONE action.
4748

48-
**Kind**: inner method of [<code>reducers.lookup</code>](#module_reducers.lookup)
49-
**Returns**: <code>Object</code> - New state
49+
**Kind**: inner method of [<code>reducers.lookup</code>](#module_reducers.lookup)
50+
**Returns**: <code>Object</code> - New state
51+
52+
| Param | Type | Description |
53+
| --- | --- | --- |
54+
| state | <code>Object</code> | |
55+
| action | <code>Object</code> | Payload will be JSON from api call |
56+
<a name="module_reducers.lookup..onGetReviewTypesDone"></a>
57+
58+
### reducers.lookup~onGetReviewTypesDone(state, action) ⇒ <code>Object</code>
59+
Handles LOOKUP/GET_REVIEW_TYPES_DONE action.
60+
61+
**Kind**: inner method of [<code>reducers.lookup</code>](#module_reducers.lookup)
62+
**Returns**: <code>Object</code> - New state
5063

5164
| Param | Type | Description |
5265
| --- | --- | --- |
@@ -58,8 +71,8 @@ Handles LOOKUP/GET_COUNTRIES_DONE action.
5871
### reducers.lookup~create(initialState) ⇒ <code>function</code>
5972
Creates a new Lookup reducer with the specified initial state.
6073

61-
**Kind**: inner method of [<code>reducers.lookup</code>](#module_reducers.lookup)
62-
**Returns**: <code>function</code> - Lookup reducer.
74+
**Kind**: inner method of [<code>reducers.lookup</code>](#module_reducers.lookup)
75+
**Returns**: <code>function</code> - Lookup reducer.
6376

6477
| Param | Type | Description |
6578
| --- | --- | --- |

‎docs/services.submissions.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ via API V5.
1313
* [new SubmissionsService(tokenV3)](#new_module_services.submissions..SubmissionsService_new)
1414
* [.getSubmissions(filters, params)](#module_services.submissions..SubmissionsService+getSubmissions) ⇒ <code>Promise</code>
1515
* [.getSubmissionInformation(submissionId)](#module_services.submissions..SubmissionsService+getSubmissionInformation) ⇒ <code>Promise</code>
16+
* [.downloadSubmission(submissionId)](#module_services.submissions..SubmissionsService+downloadSubmission) ⇒ <code>Promise</code>
1617

1718
<a name="module_services.submissions.getService"></a>
1819

@@ -35,6 +36,7 @@ Returns a new or existing submissions service.
3536
* [new SubmissionsService(tokenV3)](#new_module_services.submissions..SubmissionsService_new)
3637
* [.getSubmissions(filters, params)](#module_services.submissions..SubmissionsService+getSubmissions) ⇒ <code>Promise</code>
3738
* [.getSubmissionInformation(submissionId)](#module_services.submissions..SubmissionsService+getSubmissionInformation) ⇒ <code>Promise</code>
39+
* [.downloadSubmission(submissionId)](#module_services.submissions..SubmissionsService+downloadSubmission) ⇒ <code>Promise</code>
3840

3941
<a name="new_module_services.submissions..SubmissionsService_new"></a>
4042

@@ -68,3 +70,15 @@ Gets submission information.
6870
| Param | Type | Description |
6971
| --- | --- | --- |
7072
| submissionId | <code>String</code> | The id of submission |
73+
74+
<a name="module_services.submissions..SubmissionsService+downloadSubmission"></a>
75+
76+
#### submissionsService.downloadSubmission(submissionId) ⇒ <code>Promise</code>
77+
Download submission.
78+
79+
**Kind**: instance method of [<code>SubmissionsService</code>](#module_services.submissions..SubmissionsService)
80+
**Returns**: <code>Promise</code> - Resolves to the submission file.
81+
82+
| Param | Type | Description |
83+
| --- | --- | --- |
84+
| submissionId | <code>String</code> | The id of submission |

‎package-lock.json

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

‎package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222
},
2323
"scripts": {
2424
"build": "npm run clean && npm run build:dev && npm run build:prod",
25-
"build:dev": "NODE_ENV=development ./node_modules/.bin/webpack --env=development --progress --profile --colors --display-optimization-bailout",
Has a comment. Original line has a comment.
25+
"build:dev": "./node_modules/.bin/webpack --env=development --progress --profile --colors --display-optimization-bailout",
2626
"build:dev:watch": "npm run clean && ./node_modules/.bin/webpack --env=development --progress --profile --colors --watch --display-optimization-bailout",
27-
"build:prod": "NODE_ENV=production ./node_modules/.bin/webpack --env=production --progress --profile --colors --display-optimization-bailout",
Has a comment. Original line has a comment.
27+
"build:prod": "./node_modules/.bin/webpack --env=production --progress --profile --colors --display-optimization-bailout",
2828
"clean": "rimraf dist",
2929
"jest": "jest --no-cache --maxWorkers=4 --config config/jest/default.js",
3030
"lint": "npm run lint:js",
@@ -48,6 +48,7 @@
4848
"redux": "^3.7.2",
4949
"redux-actions": "^2.4.0",
5050
"tc-accounts": "https://github.com/appirio-tech/accounts-app.git#dev",
51+
"tc-core-library-js": "appirio-tech/tc-core-library-js.git#v2.6",
5152
"to-capital-case": "^1.0.0",
5253
"topcoder-react-utils": "0.7.5"
5354
},

‎src/actions/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ 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';
17+
import notificationActions from './notifications';
1618

1719
export const actions = {
1820
auth: authActions.auth,
@@ -30,6 +32,8 @@ export const actions = {
3032
lookup: lookupActions.lookup,
3133
settings: settingsActions.settings,
3234
looker: lookerActions.looker,
35+
memberSearch: memberSearchActions.memberSearch,
36+
notifications: notificationActions.notifications,
3337
};
3438

3539
export default undefined;

‎src/actions/lookup.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,13 @@ function getSkillTagsInit() {}
112112
*/
113113
function getSkillTagsDone() {
114114
const params = {
115-
domain: 'SKILLS',
116-
status: 'APPROVED',
115+
filter: {
116+
domain: 'SKILLS',
117+
status: 'APPROVED',
118+
},
119+
limit: {
120+
limit: 1000,
121+
},
117122
};
118123
return getService().getTags(params);
119124
}
@@ -134,6 +139,23 @@ function getCountriesDone() {
134139
return getService().getCountries();
135140
}
136141

142+
/**
143+
* @static
144+
* @desc Creates an action that signals beginning of getting all review types.
145+
* @return {Action}
146+
*/
147+
function getReviewTypesInit() {}
148+
149+
/**
150+
* @static
151+
* @desc Creates an action that gets all review types.
152+
* @param {String} tokenV3 Optional. Auth token for Topcoder API v3.
153+
* @return {Action}
154+
*/
155+
function getReviewTypesDone(tokenV3) {
156+
return getService(tokenV3).getReviewTypes();
157+
}
158+
137159
/**
138160
* @static
139161
* @desc Creates an action that signals beginning of getting all countries api version 5.
@@ -165,6 +187,8 @@ export default createActions({
165187
GET_SKILL_TAGS_DONE: getSkillTagsDone,
166188
GET_COUNTRIES_INIT: getCountriesInit,
167189
GET_COUNTRIES_DONE: getCountriesDone,
190+
GET_REVIEW_TYPES_INIT: getReviewTypesInit,
191+
GET_REVIEW_TYPES_DONE: getReviewTypesDone,
168192
GET_ALL_COUNTRIES_INIT: getAllCountriesInit,
169193
GET_ALL_COUNTRIES_DONE: getAllCountriesDone,
170194
},

‎src/actions/member-search.js

Lines changed: 80 additions & 0 deletions
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/actions/members.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,8 @@ async function getStatsHistoryInit(handle, uuid) {
187187
* @param {String} tokenV3 v3 auth token.
188188
* @return {Action}
189189
*/
190-
async function getStatsHistoryDone(handle, uuid, tokenV3) {
191-
const data = await getService(tokenV3).getStatsHistory(handle);
190+
async function getStatsHistoryDone(handle, groupIds, uuid, tokenV3) {
191+
const data = await getService(tokenV3).getStatsHistory(handle, groupIds);
192192
return { data, handle, uuid };
193193
}
194194

‎src/actions/notifications.js

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/**
2+
* @module "actions.notifications"
3+
* @desc Actions related to notifications data.
4+
*/
5+
6+
import _ from 'lodash';
7+
import { createActions } from 'redux-actions';
8+
import { getService } from '../services/notifications';
9+
10+
/**
11+
* TODO: We will need to change this based on API and
12+
* frontend mapping we need later.
13+
*/
14+
function processData(data) {
15+
const retData = _.map(data, (item) => {
16+
const object = {};
17+
object.id = item.id;
18+
object.sourceId = item.contents.id;
19+
object.sourceName = item.contents.name || item.contents.title;
20+
object.eventType = item.type;
21+
object.isRead = item.read;
22+
object.isSeen = item.seen;
23+
object.contents = item.contents.message || item.contents.title;
24+
object.version = item.version;
25+
object.date = item.createdAt;
26+
return object;
27+
});
28+
return retData;
29+
}
30+
31+
/**
32+
* @static
33+
* @desc Creates an action that signals beginning of notifications
34+
* loading.
35+
* @return {Action}
36+
*/
37+
function getNotificationsInit() {
38+
return { };
39+
}
40+
41+
/**
42+
* @static
43+
* @desc Creates an action that loads member achievements.
44+
* @param {String} tokenV3 v3 auth token.
45+
* @return {Action}
46+
*/
47+
async function getNotificationsDone(tokenV3) {
48+
let data;
49+
try {
50+
data = await getService(tokenV3).getNotifications();
51+
} catch (e) {
52+
data = [];
53+
}
54+
return processData(data.items || []);
55+
}
56+
57+
/**
58+
* @static
59+
* @desc Creates an action that signals beginning of mark notification as read
60+
* loading.
61+
* @return {Action}
62+
*/
63+
function markNotificationAsReadInit() {
64+
return { };
65+
}
66+
67+
/**
68+
* @static
69+
* @desc Creates an action that marks notification as read.
70+
* @param {String} tokenV3 v3 auth token.
71+
* @return {Action}
72+
*/
73+
async function markNotificationAsReadDone(item, tokenV3) {
74+
try {
75+
await getService(tokenV3).markNotificationAsRead(item.id);
76+
} catch (e) {
77+
return e;
78+
}
79+
return item;
80+
}
81+
82+
/**
83+
* @static
84+
* @desc Creates an action that signals beginning of mark all notification as read
85+
* loading.
86+
* @return {Action}
87+
*/
88+
function markAllNotificationAsReadInit() {
89+
return { };
90+
}
91+
92+
/**
93+
* @static
94+
* @desc Creates an action that marks all notification as read.
95+
* @param {String} tokenV3 v3 auth token.
96+
* @return {Action}
97+
*/
98+
async function markAllNotificationAsReadDone(tokenV3) {
99+
try {
100+
await getService(tokenV3).markAllNotificationAsRead();
101+
} catch (e) {
102+
return e;
103+
}
104+
return true;
105+
}
106+
107+
108+
/**
109+
* @static
110+
* @desc Creates an action that signals beginning of mark all notification as seen
111+
* loading.
112+
* @return {Action}
113+
*/
114+
function markAllNotificationAsSeenInit() {
115+
return { };
116+
}
117+
118+
/**
119+
* @static
120+
* @desc Creates an action that marks all notification as seen.
121+
* @param {String} tokenV3 v3 auth token.
122+
* @return {Action}
123+
*/
124+
async function markAllNotificationAsSeenDone(items, tokenV3) {
125+
try {
126+
await getService(tokenV3).markAllNotificationAsSeen(items);
127+
} catch (e) {
128+
return e;
129+
}
130+
return items;
131+
}
132+
133+
134+
/**
135+
* @static
136+
* @desc Creates an action that signals beginning of dismiss all challenge notifications
137+
* loading.
138+
* @return {Action}
139+
*/
140+
function dismissChallengeNotificationsInit() {
141+
return { };
142+
}
143+
144+
/**
145+
* @static
146+
* @desc Creates an action that dismisses all challenge notifications
147+
* @param {String} tokenV3 v3 auth token.
148+
* @return {Action}
149+
*/
150+
async function dismissChallengeNotificationsDone(challengeId, tokenV3) {
151+
try {
152+
await getService(tokenV3).dismissChallengeNotifications(challengeId);
153+
} catch (e) {
154+
return e;
155+
}
156+
return true;
157+
}
158+
159+
160+
export default createActions({
161+
NOTIFICATIONS: {
162+
GET_NOTIFICATIONS_INIT: getNotificationsInit,
163+
GET_NOTIFICATIONS_DONE: getNotificationsDone,
164+
MARK_NOTIFICATION_AS_READ_INIT: markNotificationAsReadInit,
165+
MARK_NOTIFICATION_AS_READ_DONE: markNotificationAsReadDone,
166+
MARK_ALL_NOTIFICATION_AS_READ_INIT: markAllNotificationAsReadInit,
167+
MARK_ALL_NOTIFICATION_AS_READ_DONE: markAllNotificationAsReadDone,
168+
MARK_ALL_NOTIFICATION_AS_SEEN_INIT: markAllNotificationAsSeenInit,
169+
MARK_ALL_NOTIFICATION_AS_SEEN_DONE: markAllNotificationAsSeenDone,
170+
DISMISS_CHALLENGE_NOTIFICATIONS_INIT: dismissChallengeNotificationsInit,
171+
DISMISS_CHALLENGE_NOTIFICATIONS_DONE: dismissChallengeNotificationsDone,
172+
},
173+
});

‎src/reducers/index.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import errors, { factory as errorsFactory } from './errors';
1212
import challenge, { factory as challengeFactory } from './challenge';
1313
import profile, { factory as profileFactory } from './profile';
1414
import members, { factory as membersFactory } from './members';
15+
import notifications, { factory as notificationsFactory } from './notifications';
1516
import lookup, { factory as lookupFactory } from './lookup';
1617
import memberTasks, { factory as memberTasksFactory } from './member-tasks';
1718
import reviewOpportunity, { factory as reviewOpportunityFactory }
@@ -22,7 +23,7 @@ import settings, { factory as settingsFactory }
2223
from './settings';
2324
import looker, { factory as lookerFactory }
2425
from './looker';
25-
26+
import memberSearch, { factory as memberSearchFactory } from './member-search';
2627

2728
export function factory(options) {
2829
return redux.resolveReducers({
@@ -41,6 +42,8 @@ export function factory(options) {
4142
mySubmissionsManagement: mySubmissionsManagementFactory(options),
4243
settings: settingsFactory(options),
4344
looker: lookerFactory(options),
45+
memberSearch: memberSearchFactory(options),
46+
notifications: notificationsFactory(options),
4447
});
4548
}
4649

@@ -60,4 +63,6 @@ export default ({
6063
mySubmissionsManagement,
6164
settings,
6265
looker,
66+
memberSearch,
67+
notifications,
6368
});

‎src/reducers/lookup.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,25 @@ function onGetCountriesDone(state, { payload, error }) {
198198
});
199199
}
200200

201+
/**
202+
* Handles LOOKUP/GET_REVIEW_TYPES_DONE action.
203+
* @param {Object} state
204+
* @param {Object} action Payload will be JSON from api call
205+
* @return {Object} New state
206+
*/
207+
function onGetReviewTypesDone(state, { payload, error }) {
208+
if (error) {
209+
logger.error('Failed to get review types', payload);
210+
return { ...state, loadingReviewTypesError: true };
211+
}
212+
213+
return ({
214+
...state,
215+
loadingReviewTypesError: false,
216+
reviewTypes: payload,
217+
});
218+
}
219+
201220
/**
202221
* Handles LOOKUP/GET_ALL_COUNTRIES_DONE action.
203222
* @param {Object} state
@@ -237,6 +256,8 @@ function create(initialState = {}) {
237256
[a.getSkillTagsDone]: onGetSkillTagsDone,
238257
[a.getCountriesInit]: state => state,
239258
[a.getCountriesDone]: onGetCountriesDone,
259+
[a.getReviewTypesInit]: state => state,
260+
[a.getReviewTypesDone]: onGetReviewTypesDone,
240261
[a.getAllCountriesInit]: state => state,
241262
[a.getAllCountriesDone]: onGetAllCountriesDone,
242263
}, _.defaults(initialState, {
@@ -251,6 +272,7 @@ function create(initialState = {}) {
251272
oses: [],
252273
osPage: 1,
253274
hasMoreOses: false,
275+
reviewTypes: [],
254276
}));
255277
}
256278

‎src/reducers/member-search.js

Lines changed: 245 additions & 0 deletions
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/reducers/notifications.js

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
/**
2+
* @module "reducers.notifications"
3+
* @desc Reducer for {@link module:actions.notifications} actions.
4+
*
5+
* State segment managed by this reducer has the following strcuture:
6+
* @param {Array} authenticating=true `true` if authentication is still in
7+
* progress; `false` if it has already completed or failed.
8+
* @param {Object} profile=null Topcoder user profile.
9+
* @param {String} tokenV2='' Topcoder v2 auth token.
10+
* @param {String} tokenV3='' Topcoder v3 auth token.
11+
* @param {Object} user=null Topcoder user object (user information stored in
12+
* v3 auth token).
13+
*/
14+
15+
16+
import { handleActions } from 'redux-actions';
17+
import actions from '../actions/notifications';
18+
import logger from '../utils/logger';
19+
import { fireErrorMessage } from '../utils/errors';
20+
21+
22+
/**
23+
* Handles NOTIFICATIONS/GET_NOTIFICATIONS_INIT action.
24+
* @param {Object} state
25+
* @return {Object} New state
26+
*/
27+
function onGetNotificationsInit(state) {
28+
return { ...state };
29+
}
30+
31+
/**
32+
* Handles NOTIFICATIONS/GET_NOTIFICATIONS_DONE action.
33+
* @param {Object} state
34+
* @param {Object} action
35+
* @return {Object} New state.
36+
*/
37+
function onGetNotificationsDone(state, { error, payload }) {
38+
if (error) {
39+
logger.error('Failed to get notifications!', payload);
40+
fireErrorMessage(
41+
'ERROR: Failed to load the notifications',
42+
'Please, try again a bit later',
43+
);
44+
return {
45+
...state,
46+
fetchNotificationsFailure: true,
47+
items: [],
48+
};
49+
}
50+
51+
return {
52+
...state,
53+
items: payload,
54+
fetchNotificationsFailure: false,
55+
};
56+
}
57+
58+
/**
59+
* Handles NOTIFICATIONS/MARK_NOTIFICATION_AS_INIT action.
60+
* @param {Object} state
61+
* @return {Object} New state
62+
*/
63+
function onMarkNotificationAsReadInit(state) {
64+
return { ...state };
65+
}
66+
67+
/**
68+
* Handles NOTIFICATIONS/MARK_NOTIFICATION_AS_DONE action.
69+
* @param {Object} state
70+
* @param {Object} action
71+
* @return {Object} New state.
72+
*/
73+
function onMarkNotificationAsReadDone(state, { error, payload }) {
74+
if (error) {
75+
logger.error('Failed to mark notification as read!', payload);
76+
fireErrorMessage(
77+
'ERROR: Failed to mark the notification as read',
78+
'Please, try again a bit later',
79+
);
80+
return {
81+
...state,
82+
fetchNotificationsFailure: true,
83+
};
84+
}
85+
86+
const notifications = state.items;
87+
const itemIndex = state.items.findIndex(item => item.id === payload.id);
88+
notifications[itemIndex].isRead = true;
89+
90+
return {
91+
...state,
92+
fetchNotificationsFailure: false,
93+
items: notifications,
94+
};
95+
}
96+
97+
98+
/**
99+
* Handles NOTIFICATIONS/MARK_ALL_NOTIFICATION_AS_READ_INIT action.
100+
* @param {Object} state
101+
* @return {Object} New state
102+
*/
103+
function onMarkAllNotificationAsReadInit(state) {
104+
return { ...state };
105+
}
106+
107+
/**
108+
* Handles NOTIFICATIONS/MARK_ALL_NOTIFICATION_AS_READ_DONE action.
109+
* @param {Object} state
110+
* @param {Object} action
111+
* @return {Object} New state.
112+
*/
113+
function onMarkAllNotificationAsReadDone(state, { error, payload }) {
114+
if (error) {
115+
logger.error('Failed to mark notification as read!', payload);
116+
fireErrorMessage(
117+
'ERROR: Failed to mark the notification as read',
118+
'Please, try again a bit later',
119+
);
120+
return {
121+
...state,
122+
fetchNotificationsFailure: true,
123+
};
124+
}
125+
126+
const notifications = state.items;
127+
notifications.forEach((item, index) => {
128+
notifications[index].isRead = true;
129+
});
130+
131+
return {
132+
...state,
133+
fetchNotificationsFailure: true,
134+
items: notifications,
135+
};
136+
}
137+
138+
/**
139+
* Handles NOTIFICATIONS/MARK_ALL_NOTIFICATION_AS_SEEN_INIT action.
140+
* @param {Object} state
141+
* @return {Object} New state
142+
*/
143+
function onMarkAllNotificationAsSeenInit(state) {
144+
return { ...state };
145+
}
146+
147+
/**
148+
* Handles NOTIFICATIONS/MARK_ALL_NOTIFICATION_AS_SEEN_DONE action.
149+
* @param {Object} state
150+
* @param {Object} action
151+
* @return {Object} New state.
152+
*/
153+
function onMarkAllNotificationAsSeenDone(state, { error, payload }) {
154+
if (error) {
155+
logger.error('Failed to mark notification as seen!', payload);
156+
fireErrorMessage(
157+
'ERROR: Failed to mark the notification as seen',
158+
'Please, try again a bit later',
159+
);
160+
return {
161+
...state,
162+
fetchNotificationsFailure: true,
163+
};
164+
}
165+
166+
const items = payload.split('-');
167+
const notifications = state.items;
168+
state.items.forEach((item, index) => {
169+
if (items.includes(String(item.id))) {
170+
notifications[index].isSeen = true;
171+
}
172+
});
173+
174+
return {
175+
...state,
176+
fetchNotificationsFailure: false,
177+
items: notifications,
178+
};
179+
}
180+
181+
/**
182+
* Handles NOTIFICATIONS/DISMISS_CHALLENGE_NOTIFICATIONS_INIT action.
183+
* @param {Object} state
184+
* @return {Object} New state
185+
*/
186+
function onDismissChallengeNotificationsInit(state) {
187+
return { ...state };
188+
}
189+
190+
/**
191+
* Handles NOTIFICATIONS/DISMISS_CHALLENGE_NOTIFICATIONS_DONE action.
192+
* @param {Object} state
193+
* @param {Object} action
194+
* @return {Object} New state.
195+
*/
196+
function onDismissChallengeNotificationsDone(state, { error, payload }) {
197+
if (error) {
198+
logger.error('Failed to dismiss notification!', payload);
199+
fireErrorMessage(
200+
'ERROR: Failed to dismiss the notification',
201+
'Please, try again a bit later',
202+
);
203+
return {
204+
...state,
205+
fetchNotificationsFailure: true,
206+
items: [],
207+
};
208+
}
209+
210+
return {
211+
...state,
212+
fetchNotificationsFailure: false,
213+
};
214+
}
215+
216+
217+
/**
218+
* Creates a new Members reducer with the specified initial state.
219+
* @param {Object} initialState Optional. Initial state.
220+
* @return {Function} Members reducer.
221+
*/
222+
function create(initialState = {}) {
223+
const a = actions.notifications;
224+
return handleActions({
225+
[a.getNotificationsInit]: onGetNotificationsInit,
226+
[a.getNotificationsDone]: onGetNotificationsDone,
227+
[a.markNotificationAsReadInit]: onMarkNotificationAsReadInit,
228+
[a.markNotificationAsReadDone]: onMarkNotificationAsReadDone,
229+
[a.markAllNotificationAsReadInit]: onMarkAllNotificationAsReadInit,
230+
[a.markAllNotificationAsReadDone]: onMarkAllNotificationAsReadDone,
231+
[a.markAllNotificationAsSeenInit]: onMarkAllNotificationAsSeenInit,
232+
[a.markAllNotificationAsSeenDone]: onMarkAllNotificationAsSeenDone,
233+
[a.dismissChallengeNotificationsInit]: onDismissChallengeNotificationsInit,
234+
[a.dismissChallengeNotificationsDone]: onDismissChallengeNotificationsDone,
235+
}, initialState);
236+
}
237+
238+
/**
239+
* Factory which creates a new reducer with its initial state tailored to the
240+
* given options object, if specified (for server-side rendering). If options
241+
* object is not specified, it creates just the default reducer. Accepted options are:
242+
* @return {Promise}
243+
* @resolves {Function(state, action): state} New reducer.
244+
*/
245+
export function factory() {
246+
return Promise.resolve(create());
247+
}
248+
249+
/**
250+
* @static
251+
* @member default
252+
* @desc Reducer with default initial state.
253+
*/
254+
export default create();

‎src/services/api.js

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@
66
import _ from 'lodash';
77
import fetch from 'isomorphic-fetch';
88
import { config, isomorphy } from 'topcoder-react-utils';
9+
import { auth } from 'tc-core-library-js';
910
import { delay } from '../utils/time';
1011
import {
1112
setErrorIcon,
1213
ERROR_ICON_TYPES,
1314
} from '../utils/errors';
1415

16+
const { m2m: m2mAuth } = auth;
17+
18+
const m2m = m2mAuth(_.pick(config.AUTH_CONFIG, ['AUTH0_URL', 'AUTH0_AUDIENCE', 'TOKEN_CACHE_TIME', 'AUTH0_PROXY_SERVER_URL']));
19+
1520
/* The minimal delay [ms] between API calls. To avoid problems with the requests
1621
* rate limits configured in Topcoder APIs, we throttle requests rate at the
1722
* client side, and at server-side, in dev mode (which is meant to be used for
@@ -279,27 +284,9 @@ export async function getTcM2mToken() {
279284
if (!isomorphy.isServerSide()) {
280285
throw new Error('getTcM2mToken() called outside the server');
281286
}
282-
const now = Date.now();
283-
const { cached } = getTcM2mToken;
287+
284288
const { TC_M2M } = config.SECRET;
285-
if (!cached || cached.expires < now + getTcM2mToken.MIN_LIFETIME) {
286-
let res = await fetch(`https://${config.AUTH0.DOMAIN}/oauth/token`, {
287-
headers: { 'Content-Type': 'application/json' },
288-
body: JSON.stringify({
289-
client_id: TC_M2M.CLIENT_ID,
290-
client_secret: TC_M2M.CLIENT_SECRET,
291-
audience: TC_M2M.AUDIENCE,
292-
grant_type: TC_M2M.GRANT_TYPE,
293-
}),
294-
method: 'POST',
295-
});
296-
res = await res.json();
297-
getTcM2mToken.cached = {
298-
expires: now + 1000 * res.expires_in, // [ms]
299-
token: res.access_token,
300-
};
301-
}
302-
return getTcM2mToken.cached.token;
303-
}
304289

305-
getTcM2mToken.MIN_LIFETIME = 30 * 1000; // [ms]
290+
const token = await m2m.getMachineToken(TC_M2M.CLIENT_ID, TC_M2M.CLIENT_SECRET);
291+
return token;
292+
}

‎src/services/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ 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';
19+
import * as notifications from './notifications';
1820

1921
export const services = {
2022
api,
@@ -31,6 +33,8 @@ export const services = {
3133
lookup,
3234
userTraits,
3335
submissions,
36+
memberSearch,
37+
notifications,
3438
};
3539

3640
export default undefined;

‎src/services/lookup.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ class LookupService {
8383
* @return {Promise} Resolves to the tags.
8484
*/
8585
async getTags(params) {
86-
const res = await this.private.api.get(`/tags/?${qs.stringify(params)}`);
86+
const res = await this.private.api.get(`/tags/?filter=${encodeURIComponent(qs.stringify(params.filter))}&${qs.stringify(params.limit)}`);
8787
return getApiResponsePayload(res);
8888
}
8989

@@ -106,6 +106,16 @@ class LookupService {
106106
const jsonResult = await res.json();
107107
return jsonResult;
108108
}
109+
110+
/**
111+
* Gets all reviewTypes.
112+
* @return {Promise} Resolves to the review types.
113+
*/
114+
async getReviewTypes() {
115+
const res = await this.private.apiV5.get('/reviewTypes');
116+
const jsonResult = await res.json();
117+
return jsonResult;
118+
}
109119
}
110120

111121
let lastInstance = null;

‎src/services/member-search.js

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* @module "services.member-search"
3+
* @desc This module provides a service for searching members.
4+
*/
5+
import _ from 'lodash';
6+
import qs from 'qs';
7+
import { getApi } from './api';
8+
import { checkResponseSucess, mapTagToLeaderboardType } from '../utils/member-search';
9+
10+
/**
11+
* Member search service class.
12+
*/
13+
class MemberSearchService {
14+
/**
15+
* @param {String} tokenV3 Optional. Auth token for Topcoder API v3.
16+
*/
17+
constructor(tokenV3) {
18+
this.private = {
19+
api: getApi('V3', tokenV3),
20+
tokenV3,
21+
};
22+
}
23+
24+
/**
25+
* Get matched members for a search term.
26+
* @param {String} searchTerm the search term
27+
* @param {Number} offset the number of members to skip from starting
28+
* @param {Number} limit the maximum number of return members
29+
* @return {Promise} Resolves to an object containing the array of matched members
30+
* and the total count
31+
*/
32+
getUsernameMatches(searchTerm, offset, limit) {
33+
const params = {
34+
query: 'MEMBER_SEARCH',
35+
handle: encodeURIComponent(searchTerm),
36+
offset,
37+
limit,
38+
};
39+
40+
return this.private.api.get(`/members/_search/?${qs.stringify(params)}`)
41+
.then(checkResponseSucess)
42+
.then((data) => {
43+
const usernameMatches = _.get(data, 'result.content');
44+
const totalCount = _.get(data, 'result.metadata.totalCount');
45+
46+
if (!_.isArray(usernameMatches)) {
47+
throw new Error('Expected array for username response results');
48+
} else if (!_.isNumber(totalCount)) {
49+
throw new Error('Expected number for metadata total count');
50+
}
51+
52+
return {
53+
usernameMatches,
54+
totalCount,
55+
};
56+
})
57+
.catch((err) => {
58+
throw new Error(`Could not fetch username matches. Reason: ${err}`);
59+
});
60+
}
61+
62+
/**
63+
* Check if the search term is a tag.
64+
* @param {String} searchTerm the search term
65+
* @return {Promise} Resolves to a tag object
66+
*/
67+
checkIfSearchTermIsATag(searchTerm) {
68+
return this.private.api.get(`/tags/?filter=name%3D${encodeURIComponent(searchTerm)}`)
69+
.then(checkResponseSucess)
70+
.then((data) => {
71+
const tagInfo = _.get(data, 'result.content');
72+
73+
if (!_.isArray(tagInfo)) {
74+
throw new Error('Tag response must be an array');
75+
}
76+
77+
return tagInfo[0];
78+
})
79+
.catch((err) => {
80+
throw new Error(`Could not determine if search term is a tag. Reason: ${err}`);
81+
});
82+
}
83+
84+
/**
85+
* Get matched members for a search tag.
86+
* @param {Object} tag the tag
87+
* @return {Promise} Resolves to an object containing the array of matched members
88+
*/
89+
getTopMembers(tag) {
90+
const leaderboardType = mapTagToLeaderboardType(tag.domain);
91+
92+
return this.private.api.get(`/leaderboards/?filter=id%3D${tag.id}%26type%3D${leaderboardType}`)
93+
.then(checkResponseSucess)
94+
.then((data) => {
95+
const topMembers = _.get(data, 'result.content', []);
96+
97+
return {
98+
topMembers,
99+
};
100+
})
101+
.catch((err) => {
102+
throw new Error(`Could not fetch top members. Reason: ${err}`);
103+
});
104+
}
105+
}
106+
107+
let lastInstance = null;
108+
109+
/**
110+
* Returns a new or existing member-search service.
111+
* @param {String} tokenV3 Optional. Auth token for Topcoder API v3.
112+
* @return {MemberSearchService} Member search service object
113+
*/
114+
export function getService(tokenV3) {
115+
if (!lastInstance || tokenV3 !== lastInstance.private.tokenV3) {
116+
lastInstance = new MemberSearchService(tokenV3);
117+
}
118+
return lastInstance;
119+
}
120+
121+
export default undefined;

‎src/services/members.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,13 @@ class MembersService {
9999
* @param {String} handle
100100
* @return {Promise} Resolves to the stats object.
101101
*/
102-
async getStatsHistory(handle) {
103-
const res = await this.private.api.get(`/members/${handle}/stats/history`);
102+
async getStatsHistory(handle, groupIds) {
103+
let res;
104+
if (groupIds) {
105+
res = await this.private.api.get(`/members/${handle}/stats/history?groupIds=${groupIds}`);
106+
} else {
107+
res = await this.private.api.get(`/members/${handle}/stats/history`);
108+
}
104109
return getApiResponsePayload(res);
105110
}
106111

‎src/services/notifications.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* @module "services.notifications"
3+
* @desc This module provides a service for searching for Topcoder
4+
* notifications.
5+
*/
6+
7+
import { getApi } from './api';
8+
9+
/**
10+
* Service class for Notifications.
11+
*/
12+
class NotificationService {
13+
/**
14+
* @param {String} tokenV3 Optional. Auth token for Topcoder API v5.
15+
*/
16+
constructor(tokenV3) {
17+
this.private = {
18+
apiV5: getApi('V5', tokenV3),
19+
tokenV3,
20+
};
21+
}
22+
23+
/**
24+
* Gets member's notification information.
25+
* @return {Promise} Resolves to the notification information object.
26+
*/
27+
async getNotifications() {
28+
return this.private.apiV5.get('/notifications/?platform=community&limit=20')
29+
.then(res => (res.ok ? res.json() : new Error(res.statusText)));
30+
}
31+
32+
/**
33+
* Marks given notification as read.
34+
* @return {Promise} Resolves to the notification information object.
35+
*/
36+
async markNotificationAsRead(item) {
37+
return this.private.apiV5.put(`/notifications/${item}/read`)
38+
.then(res => (res.ok ? null : Promise.reject(new Error(res.statusText))));
39+
}
40+
41+
/**
42+
* Marks all notification as read.
43+
* @return {Promise} Resolves to the notification information object.
44+
*/
45+
async markAllNotificationAsRead() {
46+
return this.private.apiV5.put('/notifications/read')
47+
.then(res => (res.ok ? null : Promise.reject(new Error(res.statusText))));
48+
}
49+
50+
/**
51+
* Marks all notification as seen.
52+
* @return {Promise} Resolves to the notification information object.
53+
*/
54+
async markAllNotificationAsSeen(items) {
55+
return this.private.apiV5.put(`/notifications/${items}/seen`)
56+
.then(res => (res.ok ? null : Promise.reject(new Error(res.statusText))));
57+
}
58+
59+
/**
60+
* Dismiss challenge notifications.
61+
* @return {Promise} Resolves to the notification information object.
62+
*/
63+
async dismissChallengeNotifications(challengeID) {
64+
return this.private.apiV5.put(`/notifications/${challengeID}/dismiss`)
65+
.then(res => (res.ok ? null : Promise.reject(new Error(res.statusText))));
66+
}
67+
}
68+
69+
let lastInstance = null;
70+
/**
71+
* Returns a new or existing notifications service.
72+
* @param {String} tokenV3 Optional. Auth token for Topcoder API v3.
73+
* @return {NotificationService} Notification service object
74+
*/
75+
export function getService(tokenV3) {
76+
if (!lastInstance || tokenV3 !== lastInstance.private.tokenV3) {
77+
lastInstance = new NotificationService(tokenV3);
78+
}
79+
return lastInstance;
80+
}
81+
82+
/* Using default export would be confusing in this case. */
83+
export default undefined;

‎src/services/submissions.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ class SubmissionsService {
5050
.then(res => (res.ok ? res.json() : new Error(res.statusText)))
5151
.then(res => res);
5252
}
53+
54+
/**
55+
* Download submission.
56+
* @param {Number|String} submissionId Submission ID.
57+
* @return {Promise} Resolves to the list of submission object.
58+
*/
59+
async downloadSubmission(submissionId) {
60+
return this.private.apiV5.get(`/submissions/${submissionId}/download`)
61+
.then(response => response.blob());
62+
}
5363
}
5464

5565
let lastInstance = null;

‎src/utils/member-search.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export function mapTagToLeaderboardType(tagDomain) {
2+
const tagToLeaderboardTypeMap = {
3+
SKILLS: 'MEMBER_SKILL',
4+
};
5+
6+
return tagDomain ? tagToLeaderboardTypeMap[tagDomain.toUpperCase()] : null;
7+
}
8+
9+
export async function checkResponseSucess(res) {
10+
if (!res.ok) {
11+
throw new Error(res.statusText);
12+
}
13+
const x = await res.json();
14+
if (!x.result.success) {
15+
throw new Error(x.result.content);
16+
}
17+
return x;
18+
}

‎src/utils/submission.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
/* eslint-disable no-param-reassign */
66
import _ from 'lodash';
77

8-
const { AV_SCAN_SCORER_REVIEW_TYPE_ID } = CONFIG;
8+
const { AV_SCAN_SCORER_REVIEW_TYPE_ID, PROVISIONAL_SCORING_COMPLETED_REVIEW_TYPE_ID } = CONFIG;
99

1010
function removeDecimal(num) {
1111
const re = new RegExp('^-?\\d+');
@@ -141,6 +141,11 @@ export function processMMSubmissions(submissions, resources, registrants) {
141141
return dateB - dateA;
142142
});
143143

144+
const provisionalScoringIsCompleted = _.some(
145+
submission.review,
146+
{ typeId: PROVISIONAL_SCORING_COMPLETED_REVIEW_TYPE_ID },
147+
);
148+
144149
const provisionalScore = toFixed(_.get(validReviews, '[0].score', '-'), 5);
145150
const finalScore = toFixed(_.get(submission, 'reviewSummation[0].aggregateScore', '-'), 5);
146151

@@ -149,6 +154,8 @@ export function processMMSubmissions(submissions, resources, registrants) {
149154
submissionTime: submission.created,
150155
provisionalScore,
151156
finalScore,
157+
provisionalScoringIsCompleted,
158+
review: submission.review,
152159
});
153160
});
154161

0 commit comments

Comments
 (0)
Please sign in to comment.