Skip to content

Commit 64409b1

Browse files
author
vikasrohit
authored
Merge pull request #4296 from appirio-tech/dev
[PROD] Beta Release 2.17.1
2 parents b0e41be + 020698e commit 64409b1

File tree

7 files changed

+216
-74
lines changed

7 files changed

+216
-74
lines changed

src/actions/loadUser.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
} from '../config/constants'
1818
import { getFreshToken, configureConnector, decodeToken } from 'tc-auth-lib'
1919
import { getUserProfile } from '../api/users'
20-
import { getUserGroups } from '../api/groups'
20+
import { fetchGroups } from '../api/groups'
2121
import { getOrgConfig } from '../api/orgConfig'
2222
import { EventTypes } from 'redux-segment'
2323

@@ -147,7 +147,10 @@ export function loadUserFailure(dispatch) {
147147
*/
148148
function loadGroups(dispatch, userId) {
149149
if (userId) {
150-
getUserGroups(userId, 'user').then((groups) => {
150+
fetchGroups({
151+
memberId: userId,
152+
membershipType: 'user'
153+
}).then((groups) => {
151154
const groupIds = _.compact(_.uniq([
152155
// get old and new ids as organizations may refer to any of them
153156
..._.map(groups, 'oldId'),

src/api/groups.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import { TC_API_URL } from '../config/constants'
44
/**
55
* Get a user groups based on it's member id and membership type
66
*
7-
* @param {String} memberId user member id
7+
* @param {Object} query fetch group query
88
*
9-
* @param {String} membershipType user membership type
9+
* @param {String} params fetch group url params
1010
*
1111
* @returns {Promise<Object>} user groups data
1212
*/
13-
export function getUserGroups(memberId, membershipType) {
14-
return axios.get(`${TC_API_URL}/v5/groups?memberId=${memberId}&membershipType=${membershipType}`)
15-
.then(resp => resp.data)
13+
export function fetchGroups(query, params = '') {
14+
const queryString = new URLSearchParams(query)
15+
return axios.get(`${TC_API_URL}/v5/groups${params}?${queryString}`)
1616
}

src/config/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,7 @@ export const CONNECT_USER = {
772772
export const PROJECT_MAX_COLORS = 5
773773

774774
export const AUTOCOMPLETE_TRIGGER_LENGTH = 3
775+
export const AUTOCOMPLETE_DEBOUNCE_TIME_MS = 150
775776

776777
// Toggle this flag to enable/disable maintenance mode
777778
export const MAINTENANCE_MODE = process.env.MAINTENANCE_MODE

src/projects/detail/components/EditProjectDefaultsForm/EditProjectDefaultsForm.jsx

Lines changed: 40 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import React from 'react'
22
import {connect} from 'react-redux'
3+
import _ from 'lodash'
34
import FormsyForm from 'appirio-tech-react-components/components/Formsy'
45
const Formsy = FormsyForm.Formsy
5-
import RadioGroup from 'appirio-tech-react-components/components/Formsy/RadioGroup'
6-
import SpecQuestionList from '../SpecQuestionList/SpecQuestionList'
7-
import Accordion from '../Accordion/Accordion'
86
import { updateProject } from '../../../actions/project'
9-
import { DEFAULT_NDA_UUID } from '../../../../config/constants'
7+
import NDAField from '../NDAField'
8+
import GroupsField from '../GroupsField'
109

1110
import './EditProjectDefaultsForm.scss'
1211

@@ -15,7 +14,6 @@ class EditProjectDefaultsForm extends React.Component {
1514
super(props)
1615

1716
this.state = {
18-
hasNda: false,
1917
enableButton: false,
2018
isLoading: true
2119
}
@@ -25,64 +23,46 @@ class EditProjectDefaultsForm extends React.Component {
2523
}
2624

2725
componentDidMount() {
28-
const {terms} = this.props.project
29-
if (terms.indexOf(DEFAULT_NDA_UUID) >= 0) {
30-
this.setState({hasNda: true})
26+
this.setState({isLoading: false, project: this.props.project})
27+
}
28+
29+
componentDidUpdate(prevProps) {
30+
if (!_.isEqual(prevProps.project, this.props.project)) {
31+
this.setState({project: this.props.project})
3132
}
32-
this.setState({isLoading: false})
3333
}
3434

35-
handleChange({nda}) {
36-
if ((nda === 'yes') !== this.state.hasNda) {
37-
if (this.state.enableButton !== true) {
38-
this.setState({enableButton: true})
39-
}
40-
} else {
41-
if (this.state.enableButton !== false) {
42-
this.setState({enableButton: false})
43-
}
35+
handleChange(changed) {
36+
const keys = _.intersection(Object.keys(changed), Object.keys(this.state.project))
37+
const reqProjectState = keys.reduce((acc, curr) => {
38+
acc[curr] = changed[curr]
39+
return acc
40+
}, {})
41+
const project = _.assign({}, this.state.project, reqProjectState)
42+
this.setState({project})
43+
const isProjectEqual = _.isEqual(this.state.project, this.props.project)
44+
if (!isProjectEqual && !this.state.enableButton) {
45+
this.setState({enableButton: true})
46+
} else if (isProjectEqual && this.state.enableButton !== false) {
47+
this.setState({enableButton: false})
4448
}
4549
}
4650

4751
handleSubmit() {
4852
const {updateProject} = this.props
49-
const {id, terms} = this.props.project
50-
const newHasNda = !this.state.hasNda
51-
if (newHasNda) {
52-
updateProject(id, {
53-
terms: [...new Set([...terms, DEFAULT_NDA_UUID])]
54-
}, true).then(() => {
55-
this.setState({
56-
hasNda: this.props.project.terms.indexOf(DEFAULT_NDA_UUID) >= 0
57-
})
58-
})
59-
} else {
60-
const newTerms = [...terms]
61-
if (newTerms.indexOf(DEFAULT_NDA_UUID) >= 0) {
62-
newTerms.splice(newTerms.indexOf(DEFAULT_NDA_UUID), 1)
63-
updateProject(id, {
64-
terms: newTerms
65-
}, true).then(() => {
66-
this.setState({
67-
hasNda: this.props.project.terms.indexOf(DEFAULT_NDA_UUID) >= 0
68-
})
69-
})
70-
}
71-
}
53+
const {id} = this.props.project
54+
const updateProjectObj = Object.keys(this.state.project)
55+
.filter(key => !_.isEqual(this.props.project[key], this.state.project[key]))
56+
.reduce((acc, curr) => {
57+
acc[curr] = this.state.project[curr]
58+
return acc
59+
}, {})
60+
updateProject(id, updateProjectObj)
61+
.then(() => this.setState({enableButton: false}))
62+
.catch(console.error)
7263
}
7364

7465
render() {
75-
const opts = [
76-
{
77-
value: 'yes',
78-
label: 'Yes'
79-
},
80-
{
81-
value: 'no',
82-
label: 'No'
83-
}
84-
]
85-
8666
if (this.state.isLoading) return null
8767

8868
return (
@@ -92,21 +72,14 @@ class EditProjectDefaultsForm extends React.Component {
9272
onChange={this.handleChange}
9373
>
9474
<div className="container">
95-
<SpecQuestionList>
96-
<Accordion
97-
title="Enforce Topcoder NDA"
98-
type="radio-group"
99-
options={opts}
100-
>
101-
<SpecQuestionList.Item>
102-
<RadioGroup
103-
name="nda"
104-
options={opts}
105-
value={this.state.hasNda ? 'yes' : 'no'}
106-
/>
107-
</SpecQuestionList.Item>
108-
</Accordion>
109-
</SpecQuestionList>
75+
<NDAField
76+
name="terms"
77+
value={this.state.project.terms}
78+
/>
79+
<GroupsField
80+
name="groups"
81+
value={this.state.project.groups}
82+
/>
11083
</div>
11184
<div className="section-footer section-footer-spec">
11285
<button
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.container {
2+
margin: 10px 0;
3+
}
4+
.fieldName {
5+
margin-bottom: 10px;
6+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import React, { PropTypes } from 'react'
2+
import _ from 'lodash'
3+
import {HOC as hoc} from 'formsy-react'
4+
5+
import FormsySelect from '../../../../components/Select/FormsySelect'
6+
import {fetchGroups} from '../../../../api/groups'
7+
import {AUTOCOMPLETE_TRIGGER_LENGTH, AUTOCOMPLETE_DEBOUNCE_TIME_MS} from '../../../../config/constants'
8+
9+
import styles from './GroupsField.module.scss'
10+
11+
class GroupsField extends React.Component {
12+
constructor(props) {
13+
super(props)
14+
15+
this.state = {
16+
groups: [],
17+
suggestions: []
18+
}
19+
20+
this.updateGroups = this.updateGroups.bind(this)
21+
this.handleChange = this.handleChange.bind(this)
22+
this.handleInputChange = this.handleInputChange.bind(this)
23+
}
24+
25+
componentDidMount() {
26+
this.updateGroups()
27+
}
28+
29+
updateGroups() {
30+
if (this.props.value && this.props.value.length > 0) {
31+
Promise.all(
32+
this.props.value
33+
.map(group => fetchGroups({}, `/${group}`))
34+
).then((groups) => groups.map(({data: group}) => ({
35+
label: group.name,
36+
value: group.id
37+
})))
38+
.then(groups => this.setState({groups}))
39+
.catch(console.error)
40+
}
41+
}
42+
43+
handleInputChange(inputValue) {
44+
const debounceFn = _.debounce(() => {
45+
if (!inputValue) return
46+
const preparedValue = inputValue.trim()
47+
if (preparedValue.length < AUTOCOMPLETE_TRIGGER_LENGTH) {
48+
this.setState({suggestions: []})
49+
return []
50+
}
51+
fetchGroups({ name: inputValue })
52+
.then(({data}) => data.map(suggestion => ({
53+
label: suggestion.name,
54+
value: suggestion.id
55+
})))
56+
.then(suggestions => this.setState({suggestions}))
57+
}, AUTOCOMPLETE_DEBOUNCE_TIME_MS)
58+
debounceFn()
59+
}
60+
61+
handleChange(groups) {
62+
this.setState({groups, suggestions: []})
63+
this.props.setValue(groups.map(group => group.value))
64+
this.props.onChange(groups)
65+
}
66+
67+
render() {
68+
return (
69+
<div className={styles.container}>
70+
<div className={styles.fieldName}>Intended Work Groups</div>
71+
<FormsySelect
72+
name="groups-field"
73+
isMulti
74+
heightAuto
75+
onInputChange={this.handleInputChange}
76+
onChange={this.handleChange}
77+
value={this.state.groups}
78+
options={this.state.suggestions}
79+
/>
80+
</div>
81+
)
82+
}
83+
}
84+
85+
GroupsField.propTypes = {
86+
name: PropTypes.string.isRequired,
87+
value: PropTypes.array.isRequired,
88+
setValue: PropTypes.func.isRequired,
89+
onChange: PropTypes.func,
90+
}
91+
92+
GroupsField.defaultProps = {
93+
onChange: () => {}
94+
}
95+
96+
export default hoc(GroupsField)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React, { PropTypes } from 'react'
2+
import { HOC as hoc } from 'formsy-react'
3+
import _ from 'lodash'
4+
import RadioGroup from 'appirio-tech-react-components/components/Formsy/RadioGroup'
5+
import { DEFAULT_NDA_UUID } from '../../../../../config/constants'
6+
7+
class NDAField extends React.Component {
8+
constructor(props) {
9+
super(props)
10+
11+
this.handleChange = this.handleChange.bind(this)
12+
}
13+
14+
handleChange(value) {
15+
const {name, value: propsValue, onChange, setValue} = this.props
16+
const newValue = [...propsValue]
17+
const idx = newValue.indexOf(DEFAULT_NDA_UUID)
18+
if (idx >= 0 && (value === 'no')) {
19+
newValue.splice(idx, 1)
20+
} else if (value === 'yes' && idx < 0) {
21+
newValue.push(DEFAULT_NDA_UUID)
22+
}
23+
setValue(newValue)
24+
onChange({[name]: newValue})
25+
}
26+
27+
render() {
28+
const opts = [
29+
{
30+
value: 'yes',
31+
label: 'Yes'
32+
},
33+
{
34+
value: 'no',
35+
label: 'No'
36+
}
37+
]
38+
return (
39+
<fieldset>
40+
<legend>Enforce Topcoder NDA</legend>
41+
<RadioGroup
42+
name="nda"
43+
options={opts}
44+
value={_.includes(this.props.value, DEFAULT_NDA_UUID) ? 'yes' : 'no'}
45+
setValue={this.handleChange}
46+
/>
47+
</fieldset>
48+
)
49+
}
50+
}
51+
52+
NDAField.propTypes = {
53+
name: PropTypes.string.isRequired,
54+
value: PropTypes.array.isRequired,
55+
setValue: PropTypes.func.isRequired,
56+
onChange: PropTypes.func.isRequired
57+
}
58+
59+
NDAField.defaultProps = {
60+
onChange: () => {}
61+
}
62+
63+
export default hoc(NDAField)

0 commit comments

Comments
 (0)