Skip to content

Better auto-complete for project searching #1565

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion src/actions/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import { fetchMemberProjects } from '../services/projects'
import {
LOAD_ALL_USER_PROJECTS_PENDING,
LOAD_ALL_USER_PROJECTS_SUCCESS,
LOAD_ALL_USER_PROJECTS_FAILURE
LOAD_ALL_USER_PROJECTS_FAILURE,
SEARCH_USER_PROJECTS_PENDING,
SEARCH_USER_PROJECTS_SUCCESS,
SEARCH_USER_PROJECTS_FAILURE
} from '../config/constants'

/**
Expand Down Expand Up @@ -33,3 +36,41 @@ export function loadAllUserProjects (isAdmin = true) {
}))
}
}

/**
* Filter projects of the authenticated user
*
* @param {bool} isAdmin is admin
* @param {string} keyword search keyword
*/
export function searchUserProjects (isAdmin = true, keyword) {
return (dispatch) => {
if (!keyword) {
dispatch({
type: SEARCH_USER_PROJECTS_SUCCESS,
projects: []
})
return
}
dispatch({
type: SEARCH_USER_PROJECTS_PENDING
})

const filters = {
sort: 'updatedAt desc',
perPage: 20,
page: 1,
keyword
}
if (!isAdmin) {
filters['memberOnly'] = true
}

fetchMemberProjects(filters).then(projects => dispatch({
type: SEARCH_USER_PROJECTS_SUCCESS,
projects
})).catch(() => dispatch({
type: SEARCH_USER_PROJECTS_FAILURE
}))
}
}
43 changes: 38 additions & 5 deletions src/components/Users/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import UserCard from '../UserCard'
import PrimaryButton from '../Buttons/PrimaryButton'
import Modal from '../Modal'
import SelectUserAutocomplete from '../SelectUserAutocomplete'
import { PROJECT_ROLES } from '../../config/constants'
import { PROJECT_ROLES, AUTOCOMPLETE_DEBOUNCE_TIME_MS } from '../../config/constants'
import { checkAdmin } from '../../util/tc'
import { addUserToProject, removeUserFromProject } from '../../services/projects'
import ConfirmationModal from '../Modal/ConfirmationModal'
Expand All @@ -31,7 +31,8 @@ class Users extends Component {
isRemoving: false,
removeError: null,
showRemoveConfirmationModal: false,
userToRemove: null
userToRemove: null,
searchKey: ''
}
this.setProjectOption = this.setProjectOption.bind(this)
this.onAddUserClick = this.onAddUserClick.bind(this)
Expand All @@ -42,6 +43,9 @@ class Users extends Component {
this.onRemoveClick = this.onRemoveClick.bind(this)
this.resetRemoveUserState = this.resetRemoveUserState.bind(this)
this.onRemoveConfirmClick = this.onRemoveConfirmClick.bind(this)
this.onInputChange = this.onInputChange.bind(this)

this.debouncedOnInputChange = _.debounce(this.onInputChange, AUTOCOMPLETE_DEBOUNCE_TIME_MS)
}

setProjectOption (projectOption) {
Expand Down Expand Up @@ -190,9 +194,31 @@ class Users extends Component {
}
}

/**
* Handler for the input which calls API for getting project suggestions
*/
onInputChange (inputValue, a, b, c) {
const { searchUserProjects } = this.props
const preparedValue = inputValue.trim()
searchUserProjects(preparedValue)
this.setState({
searchKey: preparedValue
})
}

render () {
const { projects, projectMembers, updateProjectNember, isEditable } = this.props
const projectOptions = projects.map(p => {
const {
projects,
projectMembers,
updateProjectNember,
isEditable,
isSearchingUserProjects,
resultSearchUserProjects
} = this.props
const {
searchKey
} = this.state
const projectOptions = ((searchKey ? resultSearchUserProjects : projects) || []).map(p => {
return {
label: p.name,
value: p.id
Expand All @@ -218,6 +244,10 @@ class Users extends Component {
placeholder='Select a project'
value={this.state.projectOption}
onChange={(e) => { this.setProjectOption(e) }}
onInputChange={this.debouncedOnInputChange}
isLoading={isSearchingUserProjects}
filterOption={() => true}
noOptionsMessage={() => isSearchingUserProjects ? 'Searching...' : 'No options'}
/>
</div>
</div>
Expand Down Expand Up @@ -423,8 +453,11 @@ Users.propTypes = {
addNewProjectMember: PropTypes.func.isRequired,
auth: PropTypes.object,
isEditable: PropTypes.bool,
isSearchingUserProjects: PropTypes.bool,
projects: PropTypes.arrayOf(PropTypes.object),
projectMembers: PropTypes.arrayOf(PropTypes.object)
projectMembers: PropTypes.arrayOf(PropTypes.object),
searchUserProjects: PropTypes.func.isRequired,
resultSearchUserProjects: PropTypes.arrayOf(PropTypes.object)
}

export default Users
4 changes: 4 additions & 0 deletions src/config/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ export const LOAD_ALL_USER_PROJECTS_SUCCESS = 'LOAD_ALL_USER_PROJECTS_SUCCESS'
export const LOAD_ALL_USER_PROJECTS_PENDING = 'LOAD_ALL_USER_PROJECTS_PENDING'
export const LOAD_ALL_USER_PROJECTS_FAILURE = 'LOAD_ALL_USER_PROJECTS_FAILURE'

export const SEARCH_USER_PROJECTS_SUCCESS = 'SEARCH_USER_PROJECTS_SUCCESS'
export const SEARCH_USER_PROJECTS_PENDING = 'SEARCH_USER_PROJECTS_PENDING'
export const SEARCH_USER_PROJECTS_FAILURE = 'SEARCH_USER_PROJECTS_FAILURE'

// project billingAccount
export const LOAD_PROJECT_BILLING_ACCOUNT = 'LOAD_PROJECT_BILLING_ACCOUNT'
export const LOAD_PROJECT_BILLING_ACCOUNT_PENDING = 'LOAD_PROJECT_BILLING_ACCOUNT_PENDING'
Expand Down
33 changes: 27 additions & 6 deletions src/containers/Users/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { fetchProjectById } from '../../services/projects'
import { checkAdmin } from '../../util/tc'

import {
loadAllUserProjects
loadAllUserProjects,
searchUserProjects
} from '../../actions/users'

class Users extends Component {
Expand All @@ -17,7 +18,8 @@ class Users extends Component {

this.state = {
loginUserRoleInProject: '',
projectMembers: null
projectMembers: null,
isAdmin: false
}
this.loadProject = this.loadProject.bind(this)
this.updateProjectNember = this.updateProjectNember.bind(this)
Expand All @@ -30,6 +32,9 @@ class Users extends Component {
if (!isLoading) {
const isAdmin = checkAdmin(token)
loadAllUserProjects(isAdmin)
this.setState({
isAdmin
})
}
}

Expand Down Expand Up @@ -108,10 +113,14 @@ class Users extends Component {
render () {
const {
projects,
auth
auth,
searchUserProjects,
resultSearchUserProjects,
isSearchingUserProjects
} = this.props
const {
projectMembers
projectMembers,
isAdmin
} = this.state
return (
<UsersComponent
Expand All @@ -122,7 +131,13 @@ class Users extends Component {
addNewProjectMember={this.addNewProjectMember}
projectMembers={projectMembers}
auth={auth}
isAdmin={isAdmin}
isEditable={this.isEditable()}
resultSearchUserProjects={resultSearchUserProjects}
isSearchingUserProjects={isSearchingUserProjects}
searchUserProjects={(key) => {
searchUserProjects(isAdmin, key)
}}
/>
)
}
Expand All @@ -132,6 +147,8 @@ const mapStateToProps = ({ users, auth }) => {
return {
projects: users.allUserProjects,
isLoading: users.isLoadingAllUserProjects,
resultSearchUserProjects: users.searchUserProjects,
isSearchingUserProjects: users.isSearchingUserProjects,
auth,
loggedInUser: auth.user,
token: auth.token
Expand All @@ -140,15 +157,19 @@ const mapStateToProps = ({ users, auth }) => {

Users.propTypes = {
projects: PT.arrayOf(PT.object),
resultSearchUserProjects: PT.arrayOf(PT.object),
auth: PT.object,
loggedInUser: PT.object,
token: PT.string,
isLoading: PT.bool,
loadAllUserProjects: PT.func.isRequired
isSearchingUserProjects: PT.bool,
loadAllUserProjects: PT.func.isRequired,
searchUserProjects: PT.func.isRequired
}

const mapDispatchToProps = {
loadAllUserProjects
loadAllUserProjects,
searchUserProjects
}

export default connect(mapStateToProps, mapDispatchToProps)(Users)
34 changes: 31 additions & 3 deletions src/reducers/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,50 @@
import {
LOAD_ALL_USER_PROJECTS_PENDING,
LOAD_ALL_USER_PROJECTS_SUCCESS,
LOAD_ALL_USER_PROJECTS_FAILURE
LOAD_ALL_USER_PROJECTS_FAILURE,
SEARCH_USER_PROJECTS_PENDING,
SEARCH_USER_PROJECTS_SUCCESS,
SEARCH_USER_PROJECTS_FAILURE
} from '../config/constants'

const initialState = {
allUserProjects: [],
isLoadProjectsSuccess: false
isLoadingAllUserProjects: false,
searchUserProjects: [],
isSearchingUserProjects: false
}

export default function (state = initialState, action) {
switch (action.type) {
case LOAD_ALL_USER_PROJECTS_SUCCESS:
return { ...state, allUserProjects: action.projects, isLoadingAllUserProjects: false }
return {
...state,
allUserProjects: action.projects,
isLoadingAllUserProjects: false
}
case LOAD_ALL_USER_PROJECTS_PENDING:
return { ...state, isLoadingAllUserProjects: true }
case LOAD_ALL_USER_PROJECTS_FAILURE:
return { ...state, isLoadingAllUserProjects: false }

case SEARCH_USER_PROJECTS_SUCCESS:
return {
...state,
searchUserProjects: action.projects,
isSearchingUserProjects: false
}
case SEARCH_USER_PROJECTS_PENDING:
return {
...state,
searchUserProjects: [],
isSearchingUserProjects: true
}
case SEARCH_USER_PROJECTS_FAILURE:
return {
...state,
searchUserProjects: [],
isSearchingUserProjects: false
}
default:
return state
}
Expand Down