Merge pull request #688 from guillegiu/master

Edit Ticket Title feature
This commit is contained in:
Guillermo Giuliana 2020-01-12 04:00:43 -03:00 committed by GitHub
commit 880caa0388
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 319 additions and 25 deletions

View File

@ -19,6 +19,7 @@ class ActivityRow extends React.Component {
'RE_OPEN',
'DEPARTMENT_CHANGED',
'PRIORITY_CHANGED',
'EDIT_TITLE',
'EDIT_COMMENT',
'EDIT_SETTINGS',
@ -60,6 +61,8 @@ class ActivityRow extends React.Component {
'DEPARTMENT_CHANGED',
'PRIORITY_CHANGED',
'COMMENT_EDITED',
'EDIT_TITLE',
'EDIT_COMMENT',
];
return (
@ -113,6 +116,7 @@ class ActivityRow extends React.Component {
'RE_OPEN': 'unlock-alt',
'DEPARTMENT_CHANGED': 'exchange',
'PRIORITY_CHANGED': 'exclamation',
'EDIT_TITLE': 'edit',
'EDIT_COMMENT': 'edit',
'EDIT_SETTINGS': 'wrench',

View File

@ -59,7 +59,10 @@ class TicketViewer extends React.Component {
commentEdited: false,
commentPrivate: false,
edit: false,
editId: 0
editTitle: false,
editId: 0,
newTitle: this.props.ticket.title,
editTitleError: false
};
componentDidMount() {
@ -72,13 +75,7 @@ class TicketViewer extends React.Component {
const ticket = this.props.ticket;
return (
<div className="ticket-viewer">
<div className="ticket-viewer__header row">
<span className="ticket-viewer__number">#{ticket.ticketNumber}</span>
<span className="ticket-viewer__title">{ticket.title}</span>
<span className="ticket-viewer__flag">
<Icon name={(ticket.language === 'en') ? 'us' : ticket.language}/>
</span>
</div>
{this.state.editTitle ? this.renderEditableTitle() : this.renderTitleHeader()}
{this.props.editable ? this.renderEditableHeaders() : this.renderHeaders()}
<div className="ticket-viewer__content">
<TicketEvent
@ -105,6 +102,50 @@ class TicketViewer extends React.Component {
);
}
renderTitleHeader() {
const {ticket, userStaff, userId} = this.props;
const {ticketNumber, title, author, editedTitle, language} = ticket;
return(
<div className="ticket-viewer__header row">
<span className="ticket-viewer__number">#{ticketNumber}</span>
<span className="ticket-viewer__title">{title}</span>
<span className="ticket-viewer__flag">
<Icon name={(language === 'en') ? 'us' : language}/>
</span>
{((author.id == userId && author.staff == userStaff) || userStaff) ? this.renderEditTitleOption() : null}
{editedTitle ? this.renderEditedTitleText() : null }
</div>
)
}
renderEditedTitleText(){
return(
<div className="ticket-viewer__edited-title-text"> {i18n('TITLE_EDITED')} </div>
)
}
renderEditTitleOption() {
return(
<span className="ticket-viewer__edit-title-icon">
<Icon name="pencil" onClick={() => this.setState({editTitle: true})} />
</span>
)
}
renderEditableTitle(){
return(
<div className="ticket-viewer__header row">
<div className="ticket-viewer__edit-title-box">
<FormField className="ticket-viewer___input-edit-title" error={this.state.editTitleError} value={this.state.newTitle} field='input' onChange={(e) => this.setState({newTitle: e.target.value })} />
</div>
<Button type='secondary' size="extra-small" onClick={this.changeTitle.bind(this)}>
{i18n('EDIT_TITLE')}
</Button>
</div>
)
}
renderEditableHeaders() {
const ticket = this.props.ticket;
const departments = this.getDepartmentsForTransfer();
@ -392,6 +433,26 @@ class TicketViewer extends React.Component {
AreYouSure.openModal(null, this.deleteTicket.bind(this));
}
changeTitle(){
API.call({
path: '/ticket/edit-title',
data: {
ticketNumber: this.props.ticket.ticketNumber,
title: this.state.newTitle
}
}).then(() => {
this.setState({
editTitle: false,
editTitleError: false
});
this.onTicketModification();
}).catch((result) => {
this.setState({
editTitleError: i18n(result.message)
})
});
}
reopenTicket() {
API.call({
path: '/ticket/re-open',
@ -608,7 +669,6 @@ class TicketViewer extends React.Component {
}
export default connect((store) => {
return {
userId: store.session.userId,
userStaff: store.session.staff,

View File

@ -9,6 +9,42 @@
color: white;
font-size: 16px;
padding: 6px 0;
display: flex;
align-items:center;
justify-content:center;
position: relative;
&:hover {
.ticket-viewer__edit-title-icon {
color: $grey;
}
}
}
&__edited-title-text {
font-style: italic;
font-size: 14px;
margin-left: 10px;
}
&__edit-title-icon {
position: absolute;
color: #414A59;
right: 12px;
&:hover {
cursor:pointer;
}
}
&___input-edit-title {
color: black;
align-items:center;
justify-content: center;
margin-bottom: 6px;
margin-right: 6px;
.input__text {
height: 25px;
}
}
&__number {

View File

@ -91,6 +91,7 @@ export default {
'BAN_EMAIL': 'Ban email',
'EDIT_EMAIL': 'Edit email',
'EDIT_PASSWORD': 'Edit password',
'EDIT_TITLE': 'Edit title',
'CHANGE_EMAIL': 'Change email',
'CHANGE_PASSWORD': 'Change password',
'NAME': 'Name',
@ -233,7 +234,7 @@ export default {
'ACTIVITY_DEPARTMENT_CHANGED': 'changed department of ticket',
'ACTIVITY_PRIORITY_CHANGED': 'changed priority of ticket',
'ACTIVITY_EDIT_COMMENT': 'edited a comment of ticket',
'ACTIVITY_EDIT_TITLE': 'edited title of ticket',
'ACTIVITY_EDIT_SETTINGS': 'edited settings',
'ACTIVITY_SIGNUP': 'signed up',
'ACTIVITY_INVITE': 'invited user',
@ -360,6 +361,7 @@ export default {
'TICKET_COMMENT_ERROR': 'An error occurred while trying to add the comment.',
'NO_PERMISSION': 'You\'ve no permission to access to this page.',
'INVALID_USER': 'User id is invalid',
'INVALID_TITLE': 'invalid title',
'ERROR_RETRIEVING_TICKETS': 'An error occurred while trying to retrieve tickets.',
'ERROR_RETRIEVING_USERS': 'An error occurred while trying to retrieve users.',
'ERROR_RETRIEVING_BAN_LIST': 'An error occurred while trying to retrieve the list of banned emails.',
@ -408,6 +410,7 @@ export default {
'SERVER_CREDENTIALS_WORKING': 'Server credentials are working correctly',
'DELETE_CUSTOM_FIELD_SURE': 'Some users may be using this field. Are you sure you want to delete it?',
'TITLE_EDITED': '(title edited)',
'COMMENT_EDITED': '(comment edited)',
'LAST_7_DAYS': 'Last 7 days',
'LAST_30_DAYS': 'Last 30 days',

View File

@ -4,6 +4,7 @@ $ticketControllers->setGroupPath('/ticket');
$ticketControllers->addController(new CreateController);
$ticketControllers->addController(new EditCommentController);
$ticketControllers->addController(new EditTitleController);
$ticketControllers->addController(new CommentController);
$ticketControllers->addController(new TicketGetController);
$ticketControllers->addController(new CheckTicketController);

View File

@ -20,6 +20,7 @@ DataValidator::with('CustomValidations', true);
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_CONTENT
* @apiUse INVALID_TOKEN
*
* @apiSuccess {Object} data Empty object
*
@ -30,15 +31,39 @@ class EditCommentController extends Controller {
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'user',
'requestData' => [
'content' => [
'validation' => DataValidator::length(10, 5000),
'error' => ERRORS::INVALID_CONTENT
if(Controller::isUserSystemEnabled()){
return [
'permission' => 'user',
'requestData' => [
'content' => [
'validation' => DataValidator::length(10, 5000),
'error' => ERRORS::INVALID_CONTENT
],
'ticketNumber' => [
'validation' => DataValidator::oneOf(DataValidator::validTicketNumber(),DataValidator::nullType()),
'error' => ERRORS::INVALID_TICKET
]
]
]
];
];
} else {
return [
'permission' => 'any',
'requestData' => [
'content' => [
'validation' => DataValidator::length(10, 5000),
'error' => ERRORS::INVALID_CONTENT
],
'ticketNumber' => [
'validation' => DataValidator::oneOf(DataValidator::validTicketNumber(),DataValidator::nullType()),
'error' => ERRORS::INVALID_TICKET
],
'csrf_token' => [
'validation' => DataValidator::equals(Session::getInstance()->getToken()),
'error' => ERRORS::INVALID_TOKEN
]
]
];
}
}
public function handler() {
@ -49,7 +74,7 @@ class EditCommentController extends Controller {
$ticketevent = Ticketevent::getTicketEvent(Controller::request('ticketEventId'));
$ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber'));
if(!Controller::isStaffLogged() && ($user->id !== $ticketevent->authorUserId && $user->id !== $ticket->authorId ) ){
if(Controller::isUserSystemEnabled() && !Controller::isStaffLogged() && ($user->id !== $ticketevent->authorUserId && $user->id !== $ticket->authorId ) ){
throw new RequestException(ERRORS::NO_PERMISSION);
}

View File

@ -0,0 +1,86 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/edit-title Edit title of a ticket
* @apiVersion 4.5.0
*
* @apiName Edit title
*
* @apiGroup Ticket
*
* @apiDescription This path edits the title of a ticket.
*
* @apiPermission user
*
* @apiParam {String} title The new title of the ticket.
* @apiParam {Number} ticketNumber The number of the ticket.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TITLE
* @apiUse INVALID_TOKEN
*
* @apiSuccess {Object} data Empty object
*
*/
class EditTitleController extends Controller {
const PATH = '/edit-title';
const METHOD = 'POST';
public function validations() {
if(Controller::isUserSystemEnabled()){
return [
'permission' => 'user',
'requestData' => [
'title' => [
'validation' => DataValidator::length(1, 200),
'error' => ERRORS::INVALID_TITLE
],
'ticketNumber' => [
'validation' => DataValidator::validTicketNumber(),
'error' => ERRORS::INVALID_TICKET
]
]
];
} else {
return [
'permission' => 'any',
'requestData' => [
'title' => [
'validation' => DataValidator::length(1, 200),
'error' => ERRORS::INVALID_TITLE
],
'ticketNumber' => [
'validation' => DataValidator::validTicketNumber(),
'error' => ERRORS::INVALID_TICKET
],
'csrf_token' => [
'validation' => DataValidator::equals(Session::getInstance()->getToken()),
'error' => ERRORS::INVALID_TOKEN
]
]
];
}
}
public function handler() {
$user = Controller::getLoggedUser();
$newtitle = Controller::request('title');
$ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber'));
if(Controller::isUserSystemEnabled() && !$user->canManageTicket($ticket)) {
throw new RequestException(ERRORS::NO_PERMISSION);
}
$ticket->title = $newtitle;
$ticket->editedTitle = true;
$ticket->store();
$ticketNumber = $ticket->ticketNumber;
Log::createLog('EDIT_TITLE', $ticketNumber);
Response::respondSuccess();
}
}

View File

@ -51,7 +51,8 @@ class Ticket extends DataStore {
'authorEmail',
'authorName',
'sharedTagList',
'editedContent'
'editedContent',
'editedTitle'
);
}
@ -132,7 +133,8 @@ class Ticket extends DataStore {
'owner' => $this->ownerToArray(),
'events' => $minimized ? [] : $this->eventsToArray(),
'tags' => $this->sharedTagList->toArray(true),
'edited' => $this->editedContent
'edited' => $this->editedContent,
'editedTitle' => $this->editedTitle
];
}

View File

@ -70,6 +70,7 @@ require './ticket/delete-tag.rb'
require './ticket/add-tag.rb'
require './ticket/delete-tag.rb'
require './ticket/edit-comment.rb'
require './ticket/edit-title.rb'
require './system/disable-user-system.rb'
require './ticket/search.rb'
# require './system/get-stats.rb'

View File

@ -19,7 +19,7 @@ describe'system/disable-user-system' do
numberOftickets = $database.query("SELECT * FROM ticket WHERE author_id IS NULL AND author_email IS NOT NULL AND author_name IS NOT NULL")
(numberOftickets.num_rows).should.equal(51)
(numberOftickets.num_rows).should.equal(52)
request('/user/logout')
@ -122,7 +122,7 @@ describe'system/disable-user-system' do
(result['status']).should.equal('success')
(result['data'].size).should.equal(10)
end
it 'should be able to get system logs as admin' do
result = request('/system/get-logs', {
page: 1,
@ -220,7 +220,7 @@ describe'system/disable-user-system' do
numberOftickets= $database.query("SELECT * FROM ticket WHERE author_email IS NULL AND author_name IS NULL AND author_id IS NOT NULL" )
(numberOftickets.num_rows).should.equal(54)
(numberOftickets.num_rows).should.equal(55)
end
it 'should not enable the user system' do

View File

@ -15,7 +15,7 @@ describe '/ticket/edit-comment' do
})
ticket = $database.getRow('ticket', 'ticket made by an user', 'title')
(result['status']).should.equal('success')
(ticket['content']).should.equal('content edited by the user')
end

View File

@ -0,0 +1,76 @@
describe '/ticket/edit-title' do
request('/user/logout')
Scripts.login();
Scripts.createTicket('Valar Morghulis','content of the ticket made by an user')
ticket = $database.getRow('ticket', 'Valar Morghulis', 'title')
ticketNumber = ticket['ticket_number']
it 'should fail change title of the ticket if the title is invalid' do
result = request('/ticket/edit-title', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
title: '',
ticketNumber: ticket['ticket_number']
})
ticket = $database.getRow('ticket', ticketNumber, 'ticket_number')
(result['status']).should.equal('fail')
(result['message']).should.equal('INVALID_TITLE')
end
it 'should change title of the ticket if the author user tries it' do
result = request('/ticket/edit-title', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
title: 'Valar dohaeris',
ticketNumber: ticket['ticket_number']
})
ticket = $database.getRow('ticket', ticketNumber, 'ticket_number')
(result['status']).should.equal('success')
(ticket['title']).should.equal('Valar dohaeris')
(ticket['edited_title']).should.equal('1')
end
it 'should change the title of the ticket if staff is logged' do
request('/user/logout')
Scripts.login($staff[:email], $staff[:password], true)
result = request('/ticket/edit-title', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
title: 'Valar dohaeris by Staff',
ticketNumber: ticket['ticket_number']
})
ticket = $database.getRow('ticket', ticketNumber, 'ticket_number')
(result['status']).should.equal('success')
(ticket['title']).should.equal('Valar dohaeris by Staff')
(ticket['edited_title']).should.equal('1')
end
it 'should not change the title if the user is not the author' do
request('/user/logout')
Scripts.login($staff[:email], $staff[:password], true)
Scripts.createTicket('Winterfell')
ticket = $database.getRow('ticket', 'Winterfell', 'title')
request('/user/logout')
Scripts.login()
result = request('/ticket/edit-title', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
title: 'Casterly Rock',
ticketNumber: ticket['ticket_number']
})
(result['status']).should.equal('fail')
(result['message']).should.equal('NO_PERMISSION')
end
end