Skip to content
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
17 changes: 17 additions & 0 deletions docs/permissions.html
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,23 @@ <h2 class="anchor-container">
</div>
</div>
</div>
<div class="row border-top">
<div class="col py-2">
<div class="permission-title anchor-container">
<a href="#VIEW_COPILOTS" name="VIEW_COPILOTS" class="anchor"></a>View Copilot Team
</div>
<div class="permission-variable"><small><code>VIEW_COPILOTS</code></small></div>
<div class="text-black-50 small-text">Who should view Copilot Team.</div>
</div>
<div class="col-9 py-2">
<div>
<span class="badge badge-primary" title="Allowed Project Role">copilot</span>
</div>

<div>
</div>
</div>
</div>
<div class="row border-top">
<div class="col py-2">
<div class="permission-title anchor-container">
Expand Down
4 changes: 3 additions & 1 deletion src/components/RichTextArea/RichTextArea.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -396,8 +396,10 @@ class RichTextArea extends React.Component {
const {className, avatarUrl, authorName, titlePlaceholder, contentPlaceholder, editMode, isCreating,
isGettingComment, disableTitle, disableContent, expandedTitlePlaceholder, editingTopic, hasPrivateSwitch, canUploadAttachment, attachments, textAreaOnly } = this.props
const {editorExpanded, editorState, titleValue, oldMDContent, currentMDContent, uploading, isPrivate, isAddLinkOpen, rawFiles, files} = this.state
const emptyStringRegex = /^[\s\n\u200B]*$/g // empty string with space, new line, zero width space character

let canSubmit = (disableTitle || titleValue.trim())
&& (disableContent || editorState.getCurrentContent().hasText())
&& (disableContent || !emptyStringRegex.test(currentMDContent))
if (editMode && canSubmit) {
canSubmit = (!disableTitle && titleValue !== this.props.oldTitle) || (!disableContent && oldMDContent !== currentMDContent)
|| (rawFiles.length > 0 || (attachments && files.length < attachments.length))
Expand Down
2 changes: 1 addition & 1 deletion src/components/TeamManagement/CopilotManagementDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ class ProjectManagementDialog extends React.Component {
</span>
</div>
</div>
{(canManageCopilots || canRemoveCopilots) && <div className="member-remove" onClick={remove}>
{(canManageCopilots || canRemoveCopilots || (currentUser.userId === member.userId)) && <div className="member-remove" onClick={remove}>
{(currentUser.userId === member.userId) ? 'Leave' : 'Remove'}
</div>}
</div>
Expand Down
5 changes: 3 additions & 2 deletions src/components/TeamManagement/TeamManagement.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ class TeamManagement extends React.Component {
const topcoderTeamManageAction = hasPermission(PERMISSIONS.MANAGE_TOPCODER_TEAM)
const copilotTeamManageAction = hasPermission(PERMISSIONS.MANAGE_COPILOTS)
const copilotRemoveAction = hasPermission(PERMISSIONS.REMOVE_COPILOTS)
const copilotViewAction = hasPermission(PERMISSIONS.VIEW_COPILOTS)
const canRequestCopilot = hasPermission(PERMISSIONS.REQUEST_COPILOTS)
const canJoinTopcoderTeam = !currentMember && hasPermission(PERMISSIONS.JOIN_TOPCODER_TEAM)

Expand Down Expand Up @@ -192,9 +193,9 @@ class TeamManagement extends React.Component {
<div className="projects-team">
<div className="title">
<span styleName="title-text">Copilot</span>
{(copilotTeamManageAction || copilotRemoveAction) &&
{(copilotTeamManageAction || copilotRemoveAction || copilotViewAction) &&
<span className="title-action" onClick={() => onShowCopilotDialog(true)}>
Manage
{(copilotTeamManageAction || copilotRemoveAction) ? 'Manage' : 'View'}
</span>
}
</div>
Expand Down
9 changes: 9 additions & 0 deletions src/config/permissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,15 @@ export const PERMISSIONS = {
]
},

VIEW_COPILOTS: {
meta: {
group: 'Project Members',
title: 'View Copilot Team',
description: 'Who should view Copilot Team.',
},
projectRoles: [PROJECT_ROLE_COPILOT],
},

MANAGE_TOPCODER_TEAM: {
meta: {
group: 'Project Members',
Expand Down
30 changes: 26 additions & 4 deletions src/helpers/markdownToState.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {convertFromRaw} from 'draft-js'
import sanitizeHtml from 'sanitize-html'
import Alert from 'react-s-alert'
const Remarkable = require('remarkable')

// Block level items, key is Remarkable's key for them, value returned is
Expand Down Expand Up @@ -257,6 +258,11 @@ function markdownToState(markdown, options = {}) {
const BlockEntities = Object.assign({}, DefaultBlockEntities, options.blockEntities || {})
const BlockStyles = Object.assign({}, DefaultBlockStyles, options.blockStyles || {})

// when there is no content, add empty paragraph
if (parsedData.length === 0) {
blocks.push(getNewBlock(BlockTypes['paragraph_open']()))
}

parsedData.forEach((item) => {

if (item.type === 'bullet_list_open') {
Expand Down Expand Up @@ -347,10 +353,26 @@ function markdownToState(markdown, options = {}) {
}, DefaultBlockType)
}

return convertFromRaw({
entityMap,
blocks,
})
let result
try {
result = convertFromRaw({
entityMap,
blocks,
})
} catch(error) {
// If any error occurs set value to plain text
const plainTextBlock = getNewBlock(BlockTypes['paragraph_open']())
plainTextBlock.text = markdown

result = convertFromRaw({
entityMap: [],
blocks: [plainTextBlock],
})

Alert.warning('Some message could not be rendered properly, please contact Topcoder Support')
}

return result
}

export default markdownToState
37 changes: 13 additions & 24 deletions src/projects/create/components/ProjectSubmitted.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,37 @@ import React from 'react'
import PT from 'prop-types'

require('./ProjectSubmitted.scss')
import {
CONNECT_DOMAIN
} from '../../../config/constants'

class ProjectSubmitted extends React.Component {
constructor(props) {
super(props)

this.copyToClipboard = this.copyToClipboard.bind(this)
this.state = {
domain: `${CONNECT_DOMAIN}/`,
url: `projects/${props.params.status || props.projectId}`
}
}

copyToClipboard() {
const textField = document.createElement('textarea')
textField.innerText = `${this.state.domain}${this.state.url}`
document.body.appendChild(textField)
textField.select()
document.execCommand('copy')
textField.remove()
}

render() {
return (
<div className="ProjectSubmitted flex column middle center tc-ui">
<div className="container flex column middle center">
<div className="title">Congratulations!</div>
<div className="sub-title">Your project has been submitted</div>
<div className="sub-title">Your project has been created</div>
<div className="content">
A member of our team will be reaching out to you shortly to finalize the scope and build your project plan.
Topcoder will be contacting you soon to discuss your project proposal.
<br />
<br />
Use the link below to share your project with members of your team. You can also access all your Topcoder projects in one place from your Connect project dashboard.
</div>
<div className="project-link-container flex row middle center">
{ `${this.state.domain}${this.state.url}` }
<span>
In the meantime, get a jump on the process by inviting your coworkers to your project and securely share any detailed requirements documents you have inside your project.
</span>
</div>
<div className="button-container flex row middle center">
<a type="button" onClick={this.copyToClipboard} className="copy-link-btn tc-btn tc-btn-sm tc-btn-default flex middle center" disabled={false}>Copy link</a>
<a href={this.state.url} type="button" className="go-to-project-dashboard-btn tc-btn tc-btn-sm tc-btn-primary flex middle center" disabled={false}>Go to project dashboard</a>
<a type="button" href={this.state.url} className="go-to-project-btn tc-btn tc-btn-sm tc-btn-default flex middle center" disabled={false}>
Go to Project
<small>Invite your team members and share requirements</small>
</a>
<a href="projects" type="button" className="go-to-project-dashboard-btn tc-btn tc-btn-sm tc-btn-primary flex middle center" disabled={false}>
All Projects
<small>View all of your projects</small>
</a>
</div>
</div>
</div>
Expand Down
23 changes: 19 additions & 4 deletions src/projects/create/components/ProjectSubmitted.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@

.content {
color: $tc-gray-100;
font-size: 15px;
text-align: center;
font-size: 20px;
font-weight: 400;
margin-top: 50px;
line-height: 25px;
Expand All @@ -56,13 +57,27 @@
}

.button-container {
margin-top: 30px;
.copy-link-btn {
display: flex;
flex-direction: row;
flex-wrap: wrap;
a {
display: flex;
flex-direction: column;
height: 60px;
padding: 20px 10px;
line-height: 20px;
font-size: 20px;
margin-top: 30px;
small {
font-size: 12px;
}
}
.go-to-project-btn {
margin-left: 10px;
margin-right: 10px;
}
}
}
}
}


15 changes: 11 additions & 4 deletions src/projects/detail/ProjectDetail.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const showCoderBotIfError = (hasError) => {
return branch(
(props) => {
if (props.error.code === 403 && props.error.msg.includes('Copilot')) {
const messageGenerator = `${props.error.msg.replace('Copilot: ', '')}. If things don’t work or you’re sure it is Coder’s fault, send us a note at <a href="support@topcoder.com">support@topcoder.com</a> and we’ll fix it for you.`
const messageGenerator = `${props.error.msg.replace('Copilot: ', '')}. If things don’t work or you’re sure it is Coder’s fault, send us a note at <a href="mailto:support@topcoder.com">support@topcoder.com</a> and we’ll fix it for you.`
component = compose(
withProps({code:403, message: messageGenerator})
)
Expand All @@ -52,6 +52,13 @@ const showCoderBotIfError = (hasError) => {
component = compose(
withProps({ code:props.error.code })
)
// also show error if project has `templateId` which points to the Project Template which is not found
} else if (!_.isNil(props.project.templateId) && props.projectTemplate === null) {
component = compose(
withProps({ code: 400, message: `Project Template (id ${props.project.templateId}) for this project is not found. Please, send us a note at <a href="mailto:support@topcoder.com">support@topcoder.com</a> and we’ll fix it for you.` })
)
// mark as `hasError`
return true
}
return hasError(props)
},
Expand All @@ -69,7 +76,7 @@ const spinner = spinnerWhileLoading(props =>
// first check that there are no error, before checking project properties
props.error && props.error.type === LOAD_PROJECT_FAILURE || props.error.type === ACCEPT_OR_REFUSE_INVITE_FAILURE ||
// old project or has projectTemplate loaded
((props.project && props.project.version !== 'v3') || props.projectTemplate)
((props.project && props.project.version !== 'v3') || !_.isUndefined(props.projectTemplate))
// has all product templates loaded (earlier it was checking project specific product templates only
// which can be empty when we have empty project plan config for the template)
&& props.allProductTemplates.length > 0
Expand Down Expand Up @@ -302,8 +309,8 @@ const mapStateToProps = ({projectState, projectDashboard, loadUser, productsTime
project: projectState.project,
projectNonDirty: projectState.projectNonDirty,
projectTemplate: (templateId && projectTemplates) ? (
getProjectTemplateById(projectTemplates, templateId)
) : null,
getProjectTemplateById(projectTemplates, templateId) || null // `null` means project template is not found
) : undefined, // `undefined` means the list of project templates is not yet loaded
productTemplates: (projectTemplates && productTemplates) ? (
getProjectProductTemplates(
productTemplates,
Expand Down
6 changes: 6 additions & 0 deletions src/projects/detail/components/ProjectStage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { formatNumberWithCommas } from '../../../helpers/format'
import { getPhaseActualData } from '../../../helpers/projectHelper'
import {
PROJECT_ATTACHMENTS_FOLDER,
PHASE_PRODUCT_TEMPLATE_ID,
} from '../../../config/constants'
import { filterNotificationsByPosts, filterReadNotifications, filterNotificationsByCriteria } from '../../../routes/notifications/helpers/notifications'
import { buildPhaseTimelineNotificationsCriteria, buildPhaseSpecifiationNotificationsCriteria } from '../../../routes/notifications/constants/notifications'
Expand Down Expand Up @@ -206,6 +207,10 @@ class ProjectStage extends React.Component{
}

const hasAnyNotifications = _.some(_.values(hasNotifications), _.identity)
// we don't want to show Specification tab anymore
// we still show it for old phases created with various Product Templates
// but all new phases created with one new Generic Product Template we don't show it anymore
const isGenericPhase = product.templateId === PHASE_PRODUCT_TEMPLATE_ID

return (
<PhaseCard
Expand All @@ -226,6 +231,7 @@ class ProjectStage extends React.Component{
onTabClick={this.onTabClick}
hasTimeline={hasTimeline}
hasNotifications={hasNotifications}
hideSpecTab={!hasPermission(PERMISSIONS.MANAGE_PROJECT_PLAN) || isGenericPhase}
/>

{currentActiveTab === 'timeline' &&
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import React from 'react'

import GenericMenu from '../../../../components/GenericMenu'
import { PERMISSIONS } from '../../../../config/permissions'
import { hasPermission } from '../../../../helpers/permissions'

import './ProjectStageTabs.scss'

Expand All @@ -11,6 +9,7 @@ const ProjectStageTabs = ({
hasTimeline,
onTabClick,
hasNotifications,
hideSpecTab,
}) => {
const tabs = []

Expand All @@ -30,8 +29,8 @@ const ProjectStageTabs = ({
hasNotifications: hasNotifications.posts,
})

// show specification tab for everybody expect of customers
if (hasPermission(PERMISSIONS.MANAGE_PROJECT_PLAN)) {
// hide specification tab if customers or generic phase
if (!hideSpecTab) {
tabs.push({
onClick: () => onTabClick('specification'),
label: 'Specification',
Expand Down
4 changes: 4 additions & 0 deletions src/projects/detail/containers/ProjectInfoContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ class ProjectInfoContainer extends React.Component {
}

onChangeStatus(projectId, status, reason) {
const {project} = this.props
if (project.status === status) {
return
}
const delta = {status}
if (reason && status === PROJECT_STATUS_CANCELLED) {
delta.cancelReason = reason
Expand Down
6 changes: 4 additions & 2 deletions src/projects/detail/containers/ProjectInfoContainer.scss
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,11 @@
margin-top: 20px;
padding: 5px 0;



:global {
.status-dropdown a.active {
cursor: default;
}

.panel {
// avoid double padding for editable project status
padding: 0;
Expand Down
12 changes: 6 additions & 6 deletions src/projects/list/components/Projects/Projects.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,12 @@ class Projects extends Component {
}
}
onChangeStatus(projectId, status, reason) {
const { updateProject } = this.props
const { updateProject, projects } = this.props
const delta = {status}
const currentStatus = _.find(projects, {id: projectId}).status
if (status === currentStatus) {
return
}
const pId = projectId || this.props.project.id
if (reason && status === PROJECT_STATUS_CANCELLED) {
delta.cancelReason = reason
Expand Down Expand Up @@ -331,11 +335,7 @@ class Projects extends Component {
}
}

const mapStateToProps = ({ projectSearch, members, loadUser, projectState, templates, notifications }) => {
if (projectState.project && projectState.project.id && projectSearch.projects) {
const index = _.findIndex(projectSearch.projects, {id: projectState.project.id})
projectSearch.projects.splice(index, 1, projectState.project)
}
const mapStateToProps = ({ projectSearch, members, loadUser, templates, notifications }) => {
const defaultListView = hasPermission(PERMISSIONS.SEE_GRID_VIEW_BY_DEFAULT) ? PROJECTS_LIST_VIEW.GRID : PROJECTS_LIST_VIEW.CARD
return {
currentUser : {
Expand Down
4 changes: 4 additions & 0 deletions src/projects/list/components/Projects/ProjectsGridView.scss
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ $screen-one-column: 720px;
// width: 40px;
position: relative;

.status-dropdown a.active {
cursor: default;
}

.modal-active {
position: absolute;
top: 0;
Expand Down
Loading