[DEV-162] Fix ticket comment issues (#1081)

* Fix bug in UI

* Fix bug in backend

* Fix some issues

* Improve coding

* Improve coding
This commit is contained in:
LautaroCesso 2021-11-26 17:37:33 -03:00 committed by GitHub
parent f5e9b2602c
commit 402af565a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 89 additions and 42 deletions

View File

@ -31,7 +31,9 @@ class TicketEvent extends React.Component {
private: React.PropTypes.string, private: React.PropTypes.string,
edited: React.PropTypes.bool, edited: React.PropTypes.bool,
edit: React.PropTypes.bool, edit: React.PropTypes.bool,
onToggleEdit: React.PropTypes.func onToggleEdit: React.PropTypes.func,
isLastComment: React.PropTypes.bool,
isTicketClosed: React.PropTypes.bool
}; };
state = { state = {
@ -92,12 +94,7 @@ class TicketEvent extends React.Component {
} }
renderComment() { renderComment() {
const { const { author, date, edit, file } = this.props;
author,
date,
edit,
file
} = this.props;
const customFields = (author && author.customfields) || []; const customFields = (author && author.customfields) || [];
return ( return (
@ -142,10 +139,13 @@ class TicketEvent extends React.Component {
} }
renderContent() { renderContent() {
const { content, author, userId, userStaff, isLastComment, isTicketClosed } = this.props;
const { id, staff } = author;
return ( return (
<div className="ticket-event__comment-content ql-editor"> <div className="ticket-event__comment-content ql-editor">
<div dangerouslySetInnerHTML={{__html: this.props.content}}></div> <div dangerouslySetInnerHTML={{__html: content}}></div>
{((this.props.author.id == this.props.userId && this.props.author.staff == this.props.userStaff) || this.props.userStaff) ? this.renderEditIcon() : null} {(id == userId && staff == userStaff && isLastComment && !isTicketClosed) ? this.renderEditIcon() : null }
</div> </div>
) )
} }

View File

@ -83,30 +83,46 @@ class TicketViewer extends React.Component {
render() { render() {
const { ticket, userStaff, userId, editable, allowAttachments, assignmentAllowed } = this.props; const { ticket, userStaff, userId, editable, allowAttachments, assignmentAllowed } = this.props;
const showResponseField = (!ticket.closed && (editable || !assignmentAllowed)); const { editTitle, loading, edit, editId } = this.state;
const { closed, author, content, date, edited, file, events} = ticket;
const showResponseField = (!closed && (editable || !assignmentAllowed));
const lastCommentIndex = events.map(
(event, index) => {
return {...event, index}}
).filter(
(event) => event.type === "COMMENT"
).at(-1).index;
const eventsWithModifiedComments = events.map(
(event, index) => {
return {...event, isLastComment: index === lastCommentIndex && event.type === "COMMENT"};
}
);
return ( return (
<div className="ticket-viewer"> <div className="ticket-viewer">
{this.state.editTitle ? this.renderEditableTitle() : this.renderTitleHeader()} {editTitle ? this.renderEditableTitle() : this.renderTitleHeader()}
{editable ? this.renderEditableHeaders() : this.renderHeaders()} {editable ? this.renderEditableHeaders() : this.renderHeaders()}
<div className="ticket-viewer__content"> <div className="ticket-viewer__content">
<TicketEvent <TicketEvent
loading={this.state.loading} loading={loading}
type="COMMENT" type="COMMENT"
author={ticket.author} isLastComment={!events.filter(event => event.type === "COMMENT").length}
content={userStaff ? MentionsParser.parse(ticket.content) : ticket.content} author={author}
isTicketClosed={closed}
content={userStaff ? MentionsParser.parse(content) : content}
userStaff={userStaff} userStaff={userStaff}
userId={userId} userId={userId}
date={ticket.date} date={date}
onEdit={this.onEdit.bind(this,0)} onEdit={this.onEdit.bind(this,0)}
edited={ticket.edited} edited={edited}
file={ticket.file} file={file}
edit={this.state.edit && this.state.editId == 0} edit={edit && editId == 0}
onToggleEdit={this.onToggleEdit.bind(this, 0)} onToggleEdit={this.onToggleEdit.bind(this, 0)}
allowAttachments={allowAttachments} /> allowAttachments={allowAttachments} />
</div> </div>
<div className="ticket-viewer__comments"> <div className="ticket-viewer__comments">
{ticket.events && ticket.events.map(this.renderTicketEvent.bind(this))} {eventsWithModifiedComments && eventsWithModifiedComments.map(this.renderTicketEvent.bind(this, closed))}
</div> </div>
{showResponseField ? this.renderResponseField() : this.renderReopenCloseButtons()} {showResponseField ? this.renderResponseField() : this.renderReopenCloseButtons()}
</div> </div>
@ -405,22 +421,26 @@ class TicketViewer extends React.Component {
return <Button type='link' size="medium" onClick={() => this.setState({["edit"+option]: false})}>{i18n('CLOSE')}</Button> return <Button type='link' size="medium" onClick={() => this.setState({["edit"+option]: false})}>{i18n('CLOSE')}</Button>
} }
renderTicketEvent(options, index) { renderTicketEvent(isTicketClosed, ticketEventObject, index) {
const { userStaff, ticket, userId, allowAttachments } = this.props; const { userStaff, ticket, userId, allowAttachments } = this.props;
const { edit, editId } = this.state;
const { content, author, id} = ticketEventObject;
if(userStaff && typeof options.content === 'string') { if(userStaff && typeof content === 'string') {
options.content = MentionsParser.parse(options.content); ticketEventObject.content = MentionsParser.parse(content);
} }
return ( return (
<TicketEvent <TicketEvent
{...options} {...ticketEventObject}
author={(!_.isEmpty(options.author)) ? options.author : ticket.author} isLastComment={ticketEventObject.isLastComment}
author={(!_.isEmpty(author)) ? author : ticket.author}
userStaff={userStaff} userStaff={userStaff}
isTicketClosed={isTicketClosed}
userId={userId} userId={userId}
onEdit={this.onEdit.bind(this, options.id)} onEdit={this.onEdit.bind(this, id)}
edit={this.state.edit && this.state.editId == options.id} edit={edit && editId == id}
onToggleEdit={this.onToggleEdit.bind(this, options.id)} onToggleEdit={this.onToggleEdit.bind(this, id)}
key={index} key={index}
allowAttachments={allowAttachments} /> allowAttachments={allowAttachments} />
); );
@ -735,16 +755,16 @@ class TicketViewer extends React.Component {
}) })
} }
onEdit(ticketeventid,{content}) { onEdit(ticketeventid, {content}) {
this.setState({ this.setState({
loading: true loading: true
}); });
const data = {}; const data = {};
if(ticketeventid){ if(ticketeventid) {
data.ticketEventId = ticketeventid data.ticketEventId = ticketeventid;
}else{ } else {
data.ticketNumber = this.props.ticket.ticketNumber data.ticketNumber = this.props.ticket.ticketNumber;
} }
API.call({ API.call({

View File

@ -50,21 +50,29 @@ class EditCommentController extends Controller {
$user = Controller::getLoggedUser(); $user = Controller::getLoggedUser();
$newcontent = Controller::request('content', true); $newcontent = Controller::request('content', true);
$ticketNumberLog = null; $ticketNumberLog = null;
$ticketevent = Ticketevent::getTicketEvent(Controller::request('ticketEventId')); $ticketevent = Ticketevent::getTicketEvent(Controller::request('ticketEventId'));
$ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber'));
if(!Controller::isStaffLogged() && ($user->id !== $ticketevent->authorUserId && $user->id !== $ticket->authorId ) ){ if(!$ticketevent->isNull()) {
$ticket = Ticket::getDataStore($ticketevent->ticketId);
} else {
$ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber'));
}
if(!Controller::isStaffLogged() && $user->id !== $ticketevent->authorUserId && $user->id !== $ticket->authorId) {
throw new RequestException(ERRORS::NO_PERMISSION); throw new RequestException(ERRORS::NO_PERMISSION);
} }
if(Controller::isStaffLogged()){ if(Controller::isStaffLogged() && !$user->canManageTicket($ticket)) {
if(!$ticketevent->isNull()){ throw new RequestException(ERRORS::NO_PERMISSION);
$ticket = $ticketevent->ticket; }
}
if(!$user->canManageTicket($ticket)) { if(!$ticketevent->isNull()) {
throw new RequestException(ERRORS::NO_PERMISSION); if($ticketevent->type !== "COMMENT" || $ticket->closed || $ticket->getLatestEventOfType("COMMENT")['id'] !== $ticketevent->id) {
throw new RequestException(ERRORS::INVALID_TICKET_EVENT);
}
} else {
if(sizeof($ticket->getEventsOfType("COMMENT"))) {
throw new RequestException(ERRORS::INVALID_TICKET_EVENT);
} }
} }

View File

@ -233,4 +233,23 @@ class Ticket extends DataStore {
public function isOwner($user) { public function isOwner($user) {
return !$user->isNull() && $this->owner && $user->id == $this->owner->id && ($user instanceof Staff); return !$user->isNull() && $this->owner && $user->id == $this->owner->id && ($user instanceof Staff);
} }
public function getEventsOfType($type) {
$ticketEvents = $this->eventsToArray();
$filteredEventsList = [];
foreach($ticketEvents as $ticketEvent) {
if($ticketEvent['type'] == $type) {
array_push($filteredEventsList, $ticketEvent);
}
}
return $filteredEventsList;
}
public function getLatestEventOfType($type) {
$filteredEventsList = $this->getEventsOfType($type);
return end($filteredEventsList);
}
} }