Skip to content

Commit 1b77974

Browse files
author
vikasrohit
authored
Merge pull request #3285 from appirio-tech/dev
Production release 2.4.14
2 parents bc93e97 + 687be57 commit 1b77974

File tree

73 files changed

+8841
-8618
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+8841
-8618
lines changed

package-lock.json

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

src/actions/topics.js

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
/**
2+
* Topic actions
3+
*/
4+
import _ from 'lodash'
5+
import {
6+
getTopicsWithComments,
7+
addTopicPost as addTopicPostAPI,
8+
saveTopicPost as saveTopicPostAPI,
9+
deleteTopicPost as deleteTopicPostAPI,
10+
} from '../api/messages'
11+
import {
12+
LOAD_TOPIC_MEMBERS,
13+
LOAD_TOPIC,
14+
CREATE_TOPIC_POST,
15+
DELETE_TOPIC_POST,
16+
UPDATE_TOPIC_POST,
17+
DISCOURSE_BOT_USERID,
18+
CODER_BOT_USERID,
19+
TC_SYSTEM_USERID,
20+
} from '../config/constants'
21+
import { loadMembers } from './members'
22+
import { EventTypes } from 'redux-segment'
23+
24+
/**
25+
* Load topics for given tags
26+
* @param {integer} projectId project identifier
27+
* @param {Array} tags list of tags
28+
* @param {Function} dispatch dispatch function
29+
* @return {Array} topics
30+
*/
31+
export function loadTopics(projectId, tags, dispatch) {
32+
return Promise.all(
33+
tags.map((tag) => getTopicWithoutMembers(dispatch, projectId, tag))
34+
).then((responses) => {
35+
return _.map(responses, (resp) => ({
36+
topics: _.get(resp, 'value') ? [_.get(resp, 'value')] : [],
37+
tag: _.get(resp, 'action.meta.tag'),
38+
}))
39+
})
40+
}
41+
42+
/**
43+
* Load topic for a given tag
44+
* @param {integer} projectId project identifier
45+
* @param {String} tag tag
46+
* @return {Object} action
47+
*/
48+
export function loadTopic(projectId, tag) {
49+
return (dispatch) => {
50+
return dispatch({
51+
type: LOAD_TOPIC_MEMBERS,
52+
payload: getTopicWithMember(dispatch, projectId, tag),
53+
meta: { tag }
54+
})
55+
}
56+
}
57+
58+
/**
59+
* Get topics without members
60+
* @param {Function} dispatch dispatch function
61+
* @param {integer} projectId project identifier
62+
* @param {String} tag tag
63+
* @return {Object} topic
64+
*/
65+
function getTopicWithoutMembers(dispatch, projectId, tag) {
66+
return dispatch({
67+
type: LOAD_TOPIC_MEMBERS,
68+
payload: new Promise((resolve, reject) => {
69+
return getTopicsWithComments('project', `${projectId}`, tag, false)
70+
.then((resp) => resolve(_.get(resp, 'topics[0]')))
71+
.catch(err => reject(err))
72+
}),
73+
meta: { tag }
74+
})
75+
}
76+
77+
/**
78+
* Get topics with members
79+
* @param {Function} dispatch dispatch function
80+
* @param {integer} projectId project identifier
81+
* @param {String} tag tag
82+
* @return {Promise}
83+
*/
84+
function getTopicWithMember(dispatch, projectId, tag) {
85+
return new Promise((resolve, reject) => {
86+
return dispatch({
87+
type: LOAD_TOPIC,
88+
payload: getTopicsWithComments('project', `${projectId}`, tag, false),
89+
meta: { tag }
90+
})
91+
.then(({ value }) => {
92+
let userIds = []
93+
userIds = _.union(userIds, _.map(value.topics, 'userId'))
94+
_.forEach(value.topics, topic => {
95+
userIds = _.union(userIds, _.map(topic.posts, 'userId'))
96+
})
97+
// this is to remove any nulls from the list (dev had some bad data)
98+
_.remove(userIds, i => !i || [DISCOURSE_BOT_USERID, CODER_BOT_USERID, TC_SYSTEM_USERID].indexOf(i) > -1)
99+
// return if there are no userIds to retrieve, empty result set
100+
if (!userIds.length)
101+
resolve(value.topics[0])
102+
return dispatch(loadMembers(userIds))
103+
.then(() => resolve(value.topics[0]))
104+
.catch(err => reject(err))
105+
})
106+
.catch(err => reject(err))
107+
})
108+
}
109+
110+
/**
111+
* Add post to the topic
112+
* @param {String} tag tag
113+
* @param {integer} topicId topic identifier
114+
* @param {Object} post post
115+
* @return {Object} action
116+
*/
117+
export function addTopicPost(tag, topicId, post) {
118+
return (dispatch, getState) => {
119+
const projectStatus = getState().projectState.project.status
120+
return dispatch({
121+
type: CREATE_TOPIC_POST,
122+
payload: addTopicPostAPI(topicId, post),
123+
meta: {
124+
feedId: topicId,
125+
tag,
126+
rawContent: post.content,
127+
onSuccessAnalytics: {
128+
eventType: EventTypes.track,
129+
eventPayload: {
130+
event: 'Post Created',
131+
properties: {
132+
topicCategory: tag,
133+
topicId,
134+
projectStatus
135+
}
136+
}
137+
}
138+
}
139+
})
140+
}
141+
}
142+
143+
/**
144+
* Delete post
145+
* @param {String} tag tag
146+
* @param {integer} topicId topic identifier
147+
* @param {integer} postId post identifier
148+
* @return {Object} action
149+
*/
150+
export function deleteTopicPost(tag, topicId, postId) {
151+
return (dispatch, getState) => {
152+
const projectStatus = getState().projectState.project.status
153+
return dispatch({
154+
type: DELETE_TOPIC_POST,
155+
payload: deleteTopicPostAPI(topicId, postId),
156+
meta: {
157+
feedId: topicId,
158+
tag,
159+
commentId: postId,
160+
onSuccessAnalytics: {
161+
eventType: EventTypes.track,
162+
eventPayload: {
163+
event: 'Post Deleted',
164+
properties: {
165+
topicCategory: tag,
166+
topicId,
167+
projectStatus
168+
}
169+
}
170+
}
171+
}
172+
})
173+
}
174+
}
175+
176+
/**
177+
* Update post
178+
* @param {String} tag tag
179+
* @param {integer} topicId topic identifier
180+
* @param {Object} post post
181+
* @return {Object} action
182+
*/
183+
export function updateTopicPost(tag, topicId, post) {
184+
return (dispatch, getState) => {
185+
const projectStatus = getState().projectState.project.status
186+
return dispatch({
187+
type: UPDATE_TOPIC_POST,
188+
payload: saveTopicPostAPI(topicId, post),
189+
meta: {
190+
feedId: topicId,
191+
tag,
192+
commentId: post.id,
193+
rawContent: post.content,
194+
onSuccessAnalytics: {
195+
eventType: EventTypes.track,
196+
eventPayload: {
197+
event: 'Post Saved',
198+
properties: {
199+
topicCategory: tag,
200+
topicId,
201+
projectStatus
202+
}
203+
}
204+
}
205+
}
206+
})
207+
}
208+
}

src/api/projectReports.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import _ from 'lodash'
2+
import { axiosInstance as axios } from './requestInterceptor'
3+
import { PROJECTS_API_URL } from '../config/constants'
4+
5+
/**
6+
* Get a project summary
7+
*
8+
* @param {integer} projectId unique identifier of the project
9+
*/
10+
export function getProjectSummary(projectId) {
11+
12+
const summaryPromise = axios.get(`${PROJECTS_API_URL}/v4/projects/${projectId}/reports?reportName=summary`)
13+
const budgetPromise = axios.get(`${PROJECTS_API_URL}/v4/projects/${projectId}/reports?reportName=projectBudget`)
14+
15+
return Promise.all([summaryPromise, budgetPromise]).then(responses => {
16+
const res = _.get(responses[0].data, 'result.content', {})
17+
const designMetrics = _.find(res, c => c['challenge.track'] === 'Design') || {}
18+
const totalRegistrants = _.sumBy(res, c => c['challenge.num_registrations'])
19+
20+
const res1 = _.get(responses[1].data, 'result.content', {})
21+
const filterReport = c => `${c['project_stream.tc_connect_project_id']}` === projectId.toString()
22+
const projectBudget = _.find(res1, filterReport) || {}
23+
24+
return {
25+
projectId,
26+
budget: {
27+
work: parseFloat(projectBudget['project_stream.total_actual_member_payment'] || 0),
28+
fees: parseFloat(projectBudget['project_stream.total_actual_challenge_fee'] || 0),
29+
revenue: parseFloat(projectBudget['project_stream.total_invoiced_amount'] || 0),
30+
remaining: parseFloat(projectBudget['project_stream.remaining_invoiced_budget'] || 0)
31+
},
32+
// null values will be filled in as back-end implementation/integration is done.
33+
topcoderDifference: {
34+
countries: null,
35+
registrants: totalRegistrants,
36+
designs: designMetrics['challenge.num_submissions'],
37+
linesOfCode: null,
38+
hoursSaved: null,
39+
costSavings: null,
40+
valueCreated: null,
41+
}
42+
}
43+
})
44+
}

src/api/projects.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,31 @@ export function updateProject(projectId, updatedProps, updateExisting) {
106106
})
107107
}
108108

109+
/**
110+
* Create scope change request for the given project with the given details
111+
* @param {integer} projectId project Id
112+
* @param {object} request scope change request object
113+
* @return {promise} created scope change request
114+
*/
115+
export function createScopeChangeRequest(projectId, request) {
116+
return axios.post(`${PROJECTS_API_URL}/v4/projects/${projectId}/scopeChangeRequests`, { param: request })
117+
.then(resp => {
118+
return _.get(resp.data, 'result.content')
119+
})
120+
}/**
121+
* Create scope change request for the given project with the given details
122+
* @param {integer} projectId project Id
123+
* @param {integer} requestId scope change request Id
124+
* @param {object} updatedProps updated request properties
125+
* @return {promise} updated request
126+
*/
127+
export function updateScopeChangeRequest(projectId, requestId, updatedProps) {
128+
return axios.patch(`${PROJECTS_API_URL}/v4/projects/${projectId}/scopeChangeRequests/${requestId}`, { param: updatedProps })
129+
.then(resp => {
130+
return _.get(resp.data, 'result.content')
131+
})
132+
}
133+
109134
/**
110135
* Update phase using patch
111136
* @param {integer} projectId project Id

src/api/users.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export const getPreSignedUrl = (handle, file) => {
122122
* @returns {Promise<Object>} response body
123123
*/
124124
export const checkEmailValidity = (email) => {
125-
return axios.get(`${TC_API_URL}/v3/users/validateEmail?email=${email}`)
125+
return axios.get(`${TC_API_URL}/v3/users/validateEmail?email=${encodeURIComponent(email)}`)
126126
.then(resp => _.get(resp.data, 'result.content', {}))
127127
}
128128

src/components/ActionCard/Comment.jsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react'
22
import PropTypes from 'prop-types'
33
import cn from 'classnames'
4+
import Handlebars from 'handlebars'
45
import UserTooltip from '../User/UserTooltip'
56
import RichTextArea from '../RichTextArea/RichTextArea'
67
import { Link, withRouter } from 'react-router-dom'
@@ -76,7 +77,8 @@ class Comment extends React.Component {
7677

7778
render() {
7879
const {message, author, date, edited, children, noInfo, self, isSaving, hasError, readonly, allMembers, canDelete, projectMembers, commentAnchorPrefix} = this.props
79-
const messageAnchor = commentAnchorPrefix + message.id
80+
const template = Handlebars.compile(commentAnchorPrefix)
81+
const messageAnchor = template({ postId: message.id })
8082
const messageLink = window.location.pathname.substr(0, window.location.pathname.indexOf('#')) + `#${messageAnchor}`
8183
const authorName = author ? (author.firstName + ' ' + author.lastName) : 'Connect user'
8284
const avatarUrl = _.get(author, 'photoURL', null)
@@ -174,7 +176,7 @@ class Comment extends React.Component {
174176
}
175177

176178
Comment.defaultProps = {
177-
commentAnchorPrefix: 'comment-',
179+
commentAnchorPrefix: 'comment-{{postId}}',
178180
}
179181

180182
Comment.propTypes = {

src/components/MenuItem/MenuItem.scss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,12 @@
4040

4141
.active .icon.stroke g,
4242
.active .icon.stroke path {
43-
stroke: $tc-dark-blue-100;
43+
stroke: #0AB88A;
4444
}
4545

4646
.active .icon.fill circle,
4747
.active .icon.fill path {
48-
fill: $tc-dark-blue-100;
48+
fill: #0AB88A;
4949
}
5050

5151
.active {

src/components/NotificationsDropdown/NotificationsDropdown.jsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,14 @@ class NotificationsDropdown extends React.Component {
2222

2323
toggle(isOpen) {
2424
if (typeof isOpen === 'object') {
25-
this.props.onToggle(!this.state.isOpen)
25+
if (this.props.onToggle) {
26+
this.props.onToggle(!this.state.isOpen)
27+
}
2628
this.setState({ isOpen: !this.state.isOpen})
2729
} else {
28-
this.props.onToggle(isOpen)
30+
if (this.props.onToggle) {
31+
this.props.onToggle(isOpen)
32+
}
2933
this.setState({ isOpen })
3034
}
3135
}

0 commit comments

Comments
 (0)