Skip to content

Commit 0f840ae

Browse files
authored
Merge pull request #1565 from topcoder-platform/develop
Better auto-complete for project searching
2 parents 5e6f307 + 6d97eb5 commit 0f840ae

File tree

5 files changed

+142
-15
lines changed

5 files changed

+142
-15
lines changed

src/actions/users.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import { fetchMemberProjects } from '../services/projects'
55
import {
66
LOAD_ALL_USER_PROJECTS_PENDING,
77
LOAD_ALL_USER_PROJECTS_SUCCESS,
8-
LOAD_ALL_USER_PROJECTS_FAILURE
8+
LOAD_ALL_USER_PROJECTS_FAILURE,
9+
SEARCH_USER_PROJECTS_PENDING,
10+
SEARCH_USER_PROJECTS_SUCCESS,
11+
SEARCH_USER_PROJECTS_FAILURE
912
} from '../config/constants'
1013

1114
/**
@@ -33,3 +36,41 @@ export function loadAllUserProjects (isAdmin = true) {
3336
}))
3437
}
3538
}
39+
40+
/**
41+
* Filter projects of the authenticated user
42+
*
43+
* @param {bool} isAdmin is admin
44+
* @param {string} keyword search keyword
45+
*/
46+
export function searchUserProjects (isAdmin = true, keyword) {
47+
return (dispatch) => {
48+
if (!keyword) {
49+
dispatch({
50+
type: SEARCH_USER_PROJECTS_SUCCESS,
51+
projects: []
52+
})
53+
return
54+
}
55+
dispatch({
56+
type: SEARCH_USER_PROJECTS_PENDING
57+
})
58+
59+
const filters = {
60+
sort: 'updatedAt desc',
61+
perPage: 20,
62+
page: 1,
63+
keyword
64+
}
65+
if (!isAdmin) {
66+
filters['memberOnly'] = true
67+
}
68+
69+
fetchMemberProjects(filters).then(projects => dispatch({
70+
type: SEARCH_USER_PROJECTS_SUCCESS,
71+
projects
72+
})).catch(() => dispatch({
73+
type: SEARCH_USER_PROJECTS_FAILURE
74+
}))
75+
}
76+
}

src/components/Users/index.js

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import UserCard from '../UserCard'
88
import PrimaryButton from '../Buttons/PrimaryButton'
99
import Modal from '../Modal'
1010
import SelectUserAutocomplete from '../SelectUserAutocomplete'
11-
import { PROJECT_ROLES } from '../../config/constants'
11+
import { PROJECT_ROLES, AUTOCOMPLETE_DEBOUNCE_TIME_MS } from '../../config/constants'
1212
import { checkAdmin } from '../../util/tc'
1313
import { addUserToProject, removeUserFromProject } from '../../services/projects'
1414
import ConfirmationModal from '../Modal/ConfirmationModal'
@@ -31,7 +31,8 @@ class Users extends Component {
3131
isRemoving: false,
3232
removeError: null,
3333
showRemoveConfirmationModal: false,
34-
userToRemove: null
34+
userToRemove: null,
35+
searchKey: ''
3536
}
3637
this.setProjectOption = this.setProjectOption.bind(this)
3738
this.onAddUserClick = this.onAddUserClick.bind(this)
@@ -42,6 +43,9 @@ class Users extends Component {
4243
this.onRemoveClick = this.onRemoveClick.bind(this)
4344
this.resetRemoveUserState = this.resetRemoveUserState.bind(this)
4445
this.onRemoveConfirmClick = this.onRemoveConfirmClick.bind(this)
46+
this.onInputChange = this.onInputChange.bind(this)
47+
48+
this.debouncedOnInputChange = _.debounce(this.onInputChange, AUTOCOMPLETE_DEBOUNCE_TIME_MS)
4549
}
4650

4751
setProjectOption (projectOption) {
@@ -190,9 +194,31 @@ class Users extends Component {
190194
}
191195
}
192196

197+
/**
198+
* Handler for the input which calls API for getting project suggestions
199+
*/
200+
onInputChange (inputValue, a, b, c) {
201+
const { searchUserProjects } = this.props
202+
const preparedValue = inputValue.trim()
203+
searchUserProjects(preparedValue)
204+
this.setState({
205+
searchKey: preparedValue
206+
})
207+
}
208+
193209
render () {
194-
const { projects, projectMembers, updateProjectNember, isEditable } = this.props
195-
const projectOptions = projects.map(p => {
210+
const {
211+
projects,
212+
projectMembers,
213+
updateProjectNember,
214+
isEditable,
215+
isSearchingUserProjects,
216+
resultSearchUserProjects
217+
} = this.props
218+
const {
219+
searchKey
220+
} = this.state
221+
const projectOptions = ((searchKey ? resultSearchUserProjects : projects) || []).map(p => {
196222
return {
197223
label: p.name,
198224
value: p.id
@@ -218,6 +244,10 @@ class Users extends Component {
218244
placeholder='Select a project'
219245
value={this.state.projectOption}
220246
onChange={(e) => { this.setProjectOption(e) }}
247+
onInputChange={this.debouncedOnInputChange}
248+
isLoading={isSearchingUserProjects}
249+
filterOption={() => true}
250+
noOptionsMessage={() => isSearchingUserProjects ? 'Searching...' : 'No options'}
221251
/>
222252
</div>
223253
</div>
@@ -423,8 +453,11 @@ Users.propTypes = {
423453
addNewProjectMember: PropTypes.func.isRequired,
424454
auth: PropTypes.object,
425455
isEditable: PropTypes.bool,
456+
isSearchingUserProjects: PropTypes.bool,
426457
projects: PropTypes.arrayOf(PropTypes.object),
427-
projectMembers: PropTypes.arrayOf(PropTypes.object)
458+
projectMembers: PropTypes.arrayOf(PropTypes.object),
459+
searchUserProjects: PropTypes.func.isRequired,
460+
resultSearchUserProjects: PropTypes.arrayOf(PropTypes.object)
428461
}
429462

430463
export default Users

src/config/constants.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ export const LOAD_ALL_USER_PROJECTS_SUCCESS = 'LOAD_ALL_USER_PROJECTS_SUCCESS'
6161
export const LOAD_ALL_USER_PROJECTS_PENDING = 'LOAD_ALL_USER_PROJECTS_PENDING'
6262
export const LOAD_ALL_USER_PROJECTS_FAILURE = 'LOAD_ALL_USER_PROJECTS_FAILURE'
6363

64+
export const SEARCH_USER_PROJECTS_SUCCESS = 'SEARCH_USER_PROJECTS_SUCCESS'
65+
export const SEARCH_USER_PROJECTS_PENDING = 'SEARCH_USER_PROJECTS_PENDING'
66+
export const SEARCH_USER_PROJECTS_FAILURE = 'SEARCH_USER_PROJECTS_FAILURE'
67+
6468
// project billingAccount
6569
export const LOAD_PROJECT_BILLING_ACCOUNT = 'LOAD_PROJECT_BILLING_ACCOUNT'
6670
export const LOAD_PROJECT_BILLING_ACCOUNT_PENDING = 'LOAD_PROJECT_BILLING_ACCOUNT_PENDING'

src/containers/Users/index.js

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import { fetchProjectById } from '../../services/projects'
88
import { checkAdmin } from '../../util/tc'
99

1010
import {
11-
loadAllUserProjects
11+
loadAllUserProjects,
12+
searchUserProjects
1213
} from '../../actions/users'
1314

1415
class Users extends Component {
@@ -17,7 +18,8 @@ class Users extends Component {
1718

1819
this.state = {
1920
loginUserRoleInProject: '',
20-
projectMembers: null
21+
projectMembers: null,
22+
isAdmin: false
2123
}
2224
this.loadProject = this.loadProject.bind(this)
2325
this.updateProjectNember = this.updateProjectNember.bind(this)
@@ -30,6 +32,9 @@ class Users extends Component {
3032
if (!isLoading) {
3133
const isAdmin = checkAdmin(token)
3234
loadAllUserProjects(isAdmin)
35+
this.setState({
36+
isAdmin
37+
})
3338
}
3439
}
3540

@@ -108,10 +113,14 @@ class Users extends Component {
108113
render () {
109114
const {
110115
projects,
111-
auth
116+
auth,
117+
searchUserProjects,
118+
resultSearchUserProjects,
119+
isSearchingUserProjects
112120
} = this.props
113121
const {
114-
projectMembers
122+
projectMembers,
123+
isAdmin
115124
} = this.state
116125
return (
117126
<UsersComponent
@@ -122,7 +131,13 @@ class Users extends Component {
122131
addNewProjectMember={this.addNewProjectMember}
123132
projectMembers={projectMembers}
124133
auth={auth}
134+
isAdmin={isAdmin}
125135
isEditable={this.isEditable()}
136+
resultSearchUserProjects={resultSearchUserProjects}
137+
isSearchingUserProjects={isSearchingUserProjects}
138+
searchUserProjects={(key) => {
139+
searchUserProjects(isAdmin, key)
140+
}}
126141
/>
127142
)
128143
}
@@ -132,6 +147,8 @@ const mapStateToProps = ({ users, auth }) => {
132147
return {
133148
projects: users.allUserProjects,
134149
isLoading: users.isLoadingAllUserProjects,
150+
resultSearchUserProjects: users.searchUserProjects,
151+
isSearchingUserProjects: users.isSearchingUserProjects,
135152
auth,
136153
loggedInUser: auth.user,
137154
token: auth.token
@@ -140,15 +157,19 @@ const mapStateToProps = ({ users, auth }) => {
140157

141158
Users.propTypes = {
142159
projects: PT.arrayOf(PT.object),
160+
resultSearchUserProjects: PT.arrayOf(PT.object),
143161
auth: PT.object,
144162
loggedInUser: PT.object,
145163
token: PT.string,
146164
isLoading: PT.bool,
147-
loadAllUserProjects: PT.func.isRequired
165+
isSearchingUserProjects: PT.bool,
166+
loadAllUserProjects: PT.func.isRequired,
167+
searchUserProjects: PT.func.isRequired
148168
}
149169

150170
const mapDispatchToProps = {
151-
loadAllUserProjects
171+
loadAllUserProjects,
172+
searchUserProjects
152173
}
153174

154175
export default connect(mapStateToProps, mapDispatchToProps)(Users)

src/reducers/users.js

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,50 @@
44
import {
55
LOAD_ALL_USER_PROJECTS_PENDING,
66
LOAD_ALL_USER_PROJECTS_SUCCESS,
7-
LOAD_ALL_USER_PROJECTS_FAILURE
7+
LOAD_ALL_USER_PROJECTS_FAILURE,
8+
SEARCH_USER_PROJECTS_PENDING,
9+
SEARCH_USER_PROJECTS_SUCCESS,
10+
SEARCH_USER_PROJECTS_FAILURE
811
} from '../config/constants'
912

1013
const initialState = {
1114
allUserProjects: [],
12-
isLoadProjectsSuccess: false
15+
isLoadingAllUserProjects: false,
16+
searchUserProjects: [],
17+
isSearchingUserProjects: false
1318
}
1419

1520
export default function (state = initialState, action) {
1621
switch (action.type) {
1722
case LOAD_ALL_USER_PROJECTS_SUCCESS:
18-
return { ...state, allUserProjects: action.projects, isLoadingAllUserProjects: false }
23+
return {
24+
...state,
25+
allUserProjects: action.projects,
26+
isLoadingAllUserProjects: false
27+
}
1928
case LOAD_ALL_USER_PROJECTS_PENDING:
2029
return { ...state, isLoadingAllUserProjects: true }
2130
case LOAD_ALL_USER_PROJECTS_FAILURE:
2231
return { ...state, isLoadingAllUserProjects: false }
32+
33+
case SEARCH_USER_PROJECTS_SUCCESS:
34+
return {
35+
...state,
36+
searchUserProjects: action.projects,
37+
isSearchingUserProjects: false
38+
}
39+
case SEARCH_USER_PROJECTS_PENDING:
40+
return {
41+
...state,
42+
searchUserProjects: [],
43+
isSearchingUserProjects: true
44+
}
45+
case SEARCH_USER_PROJECTS_FAILURE:
46+
return {
47+
...state,
48+
searchUserProjects: [],
49+
isSearchingUserProjects: false
50+
}
2351
default:
2452
return state
2553
}

0 commit comments

Comments
 (0)