Merge pull request from guillegiu/master

Edit tags feature
This commit is contained in:
Ivan Diaz 2019-07-01 23:06:55 -03:00 committed by GitHub
commit 74f26fb3f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 603 additions and 61 deletions

View File

@ -7,6 +7,11 @@ import API from 'lib-app/api-call';
import DateTransformer from 'lib-core/date-transformer';
import Icon from 'core-components/icon';
import Tooltip from 'core-components/tooltip';
import TextEditor from 'core-components/text-editor';
import Button from 'core-components/button';
import SubmitButton from 'core-components/submit-button';
import Form from 'core-components/form';
import FormField from 'core-components/form-field';
class TicketEvent extends React.Component {
static propTypes = {
@ -23,6 +28,13 @@ class TicketEvent extends React.Component {
content: React.PropTypes.string,
date: React.PropTypes.string,
private: React.PropTypes.string,
edited: React.PropTypes.bool,
edit: React.PropTypes.bool,
onToggleEdit: React.PropTypes.func
};
state = {
content: this.props.content
};
render() {
@ -95,12 +107,38 @@ class TicketEvent extends React.Component {
{(this.props.private*1) ? this.renderPrivateBadge() : null}
</div>
<div className="ticket-event__comment-date">{DateTransformer.transformToString(this.props.date)}</div>
<div className="ticket-event__comment-content" dangerouslySetInnerHTML={{__html: this.props.content}}></div>
{this.renderFileRow(this.props.file)}
{!this.props.edit ? this.renderContent() : this.renderEditField()}
{this.renderFooter(this.props.file)}
</div>
);
}
renderContent() {
return (
<div className="ticket-event__comment-content">
<div dangerouslySetInnerHTML={{__html: this.props.content}}></div>
{((this.props.author.id === this.props.userId) || (this.props.userStaff)) ? this.renderEditIcon() : null}
</div>
)
}
renderEditIcon() {
return (
<div className="ticket-event__comment-content__edit" >
<Icon name="pencil" onClick={this.props.onToggleEdit} />
</div>
)
}
renderEditField() {
return (
<Form loading={this.props.loading} values={{content:this.state.content}} onChange={(form) => {this.setState({content:form.content})}} onSubmit={this.props.onEdit}>
<FormField name="content" required field="textarea" validation="TEXT_AREA" fieldProps={{allowImages: this.props.allowAttachments}}/>
<div className="ticket-event__submit-edited-comment" >
<SubmitButton type="secondary" >{i18n('SUBMIT')}</SubmitButton>
<Button size="medium" onClick={this.props.onToggleEdit}>{i18n('CLOSE')}</Button>
</div>
</Form>
);
}
renderAssignment() {
let assignedTo = this.props.content;
let authorName = this.props.author.name;
@ -189,8 +227,9 @@ class TicketEvent extends React.Component {
);
}
renderFileRow(file) {
renderFooter(file) {
let node = null;
let edited = null;
if (file) {
node = <span> {this.getFileLink(file)} <Icon name="paperclip" /> </span>;
@ -198,9 +237,18 @@ class TicketEvent extends React.Component {
node = i18n('NO_ATTACHMENT');
}
if (this.props.edited && this.props.type === 'COMMENT') {
edited = i18n('COMMENT_EDITED');
}
return (
<div className="ticket-event__file">
{node}
<div className="ticket-event__items">
<div className="ticket-event__edited">
{edited}
</div>
<div className="ticket-event__file">
{node}
</div>
</div>
);
}

View File

@ -96,21 +96,47 @@
border-top: none;
padding: 20px 10px;
text-align: left;
position:relative;
img {
max-width:100%;
}
&__edit {
position:absolute;
top: 3px;
right: 9px;
align-self: right;
color:white;
:hover {
color: grey;
cursor:pointer;
}
}
}
}
&__submit-edited-comment {
display:flex;
align-items: center;
justify-content: space-between;
padding: 8px;
}
&__file {
&__items {
background-color: $very-light-grey;
cursor: pointer;
text-align: right;
padding: 5px 10px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px;
font-size: 12px;
}
&__file {
cursor: pointer;
text-align: right;
}
&__edited{
font-style: italic;
}
&__comment-badge-value {
font-weight: normal;
}

View File

@ -47,7 +47,8 @@ class TicketViewer extends React.Component {
ticket: {
author: {},
department: {},
comments: []
comments: [],
edited: null
}
};
@ -55,7 +56,9 @@ class TicketViewer extends React.Component {
loading: false,
commentValue: TextEditor.createEmpty(),
commentEdited: false,
commentPrivate: false
commentPrivate: false,
edit: false,
editId: 0
};
componentDidMount() {
@ -78,7 +81,21 @@ class TicketViewer extends React.Component {
</div>
{this.props.editable ? this.renderEditableHeaders() : this.renderHeaders()}
<div className="ticket-viewer__content">
<TicketEvent type="COMMENT" author={ticket.author} content={this.props.userStaff ? MentionsParser.parse(ticket.content) : ticket.content} date={ticket.date} file={ticket.file}/>
<TicketEvent
loading={this.state.loading}
type="COMMENT"
author={ticket.author}
content={this.props.userStaff ? MentionsParser.parse(ticket.content) : ticket.content}
userStaff={this.props.userStaff}
userId={this.props.userId}
date={ticket.date}
onEdit={this.onEdit.bind(this,0)}
edited={ticket.edited}
file={ticket.file}
edit={this.state.edit && this.state.editId == 0}
onToggleEdit={this.onToggleEdit.bind(this, 0)}
allowAttachments={this.props.allowAttachments}
/>
</div>
<div className="ticket-viewer__comments">
{ticket.events && ticket.events.map(this.renderTicketEvent.bind(this))}
@ -218,9 +235,18 @@ class TicketViewer extends React.Component {
if (this.props.userStaff && typeof options.content === 'string') {
options.content = MentionsParser.parse(options.content);
}
return (
<TicketEvent {...options} author={(!_.isEmpty(options.author)) ? options.author : this.props.ticket.author} key={index} />
<TicketEvent
{...options}
author={(!_.isEmpty(options.author)) ? options.author : this.props.ticket.author}
userStaff={this.props.userStaff}
userId={this.props.userId}
onEdit={this.onEdit.bind(this, options.id)}
edit={this.state.edit && this.state.editId == options.id}
onToggleEdit={this.onToggleEdit.bind(this, options.id)}
key={index}
allowAttachments={this.props.allowAttachments}
/>
);
}
@ -452,6 +478,51 @@ class TicketViewer extends React.Component {
}
}
onToggleEdit(ticketEventId){
this.setState({
edit: !this.state.edit,
editId: ticketEventId
})
}
onEdit(ticketeventid,{content}) {
this.setState({
loading: true
});
const data = {};
if(ticketeventid){
data.ticketeventId = ticketeventid
}else{
data.ticketNumber = this.props.ticket.ticketNumber
}
API.call({
path: '/ticket/edit-comment',
data: _.extend(
data,
TextEditor.getContentFormData(content)
)
}).then(this.onEditCommentSuccess.bind(this), this.onFailCommentFail.bind(this));
}
onEditCommentSuccess() {
this.setState({
loading: false,
commentError: false,
commentEdited: false,
edit:false
});
this.onTicketModification();
}
onFailCommentFail() {
this.setState({
loading: false,
commentError: true
});
}
onSubmit(formState) {
this.setState({
loading: true

View File

@ -35,7 +35,6 @@ import AdminPanelNewTickets from 'app/admin/panel/tickets/admin-panel-new-ticket
import AdminPanelAllTickets from 'app/admin/panel/tickets/admin-panel-all-tickets';
import AdminPanelViewTicket from 'app/admin/panel/tickets/admin-panel-view-ticket';
import AdminPanelCustomResponses from 'app/admin/panel/tickets/admin-panel-custom-responses';
import AdminPanelCustomTags from 'app/admin/panel/tickets/admin-panel-custom-tags';
import AdminPanelListUsers from 'app/admin/panel/users/admin-panel-list-users';
import AdminPanelViewUser from 'app/admin/panel/users/admin-panel-view-user';
@ -52,6 +51,7 @@ import AdminPanelViewStaff from 'app/admin/panel/staff/admin-panel-view-staff';
import AdminPanelSystemPreferences from 'app/admin/panel/settings/admin-panel-system-preferences';
import AdminPanelAdvancedSettings from 'app/admin/panel/settings/admin-panel-advanced-settings';
import AdminPanelEmailSettings from 'app/admin/panel/settings/admin-panel-email-settings';
import AdminPanelCustomTags from 'app/admin/panel/settings/admin-panel-custom-tags';
// INSTALLATION
import InstallLayout from 'app/install/install-layout';
@ -115,7 +115,6 @@ export default (
<Route path="all-tickets" component={AdminPanelAllTickets} />
<Route path="custom-responses" component={AdminPanelCustomResponses} />
<Route path="view-ticket/:ticketNumber" component={AdminPanelViewTicket} />
<Route path="custom-tags" component={AdminPanelCustomTags} />
</Route>
<Route path="users">
@ -144,6 +143,7 @@ export default (
<Route path="system-preferences" component={AdminPanelSystemPreferences} />
<Route path="advanced-settings" component={AdminPanelAdvancedSettings} />
<Route path="email-settings" component={AdminPanelEmailSettings} />
<Route path="custom-tags" component={AdminPanelCustomTags} />
</Route>
</Route>
</Route>

View File

@ -135,11 +135,6 @@ class AdminPanelMenu extends React.Component {
name: i18n('CUSTOM_RESPONSES'),
path: '/admin/panel/tickets/custom-responses',
level: 2
},
{
name: i18n('CUSTOM_TAGS'),
path: '/admin/panel/tickets/custom-tags',
level: 1
}
])
},
@ -219,6 +214,11 @@ class AdminPanelMenu extends React.Component {
name: i18n('EMAIL_SETTINGS'),
path: '/admin/panel/settings/email-settings',
level: 3
},
{
name: i18n('CUSTOM_TAGS'),
path: '/admin/panel/settings/custom-tags',
level: 3
}
])
}

View File

@ -0,0 +1,102 @@
import React from 'react';
import {connect} from 'react-redux';
import AdminPanelCustomTagsModal from 'app/admin/panel/tickets/admin-panel-custom-tags-modal';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import ConfigActions from 'actions/config-actions';
import AreYouSure from 'app-components/are-you-sure';
import ModalContainer from 'app-components/modal-container';
import Icon from 'core-components/icon';
import Button from 'core-components/button';
import Header from 'core-components/header';
import Tag from 'core-components/tag';
class AdminPanelCustomTags extends React.Component {
static propTypes = {
tags: React.PropTypes.arrayOf(
React.PropTypes.shape({
name: React.PropTypes.string,
color: React.PropTypes.string,
id: React.PropTypes.number
})
),
};
componentDidMount() {
this.retrieveCustomTags();
}
render() {
return (
<div className="admin-panel-custom-tags">
<Header title={i18n('CUSTOM_TAGS')} description={i18n('CUSTOM_TAGS_DESCRIPTION')} />
{this.renderContent()}
</div>
);
}
renderContent() {
return (
<div className="admin-panel-custom-tags__content">
<div>
<Button onClick={this.openTagModal.bind(this)} type="secondary">
{i18n('ADD_CUSTOM_TAG')}<Icon className="admin-panel-custom-tags__add-button-icon" name="plus"/>
</Button>
</div>
<div className="admin-panel-custom-tags__tag-list">
{this.props.tags.map(this.renderTag.bind(this))}
</div>
</div>
);
}
renderTag(tag, index) {
return (
<div key={index} className="admin-panel-custom-tags__tag-container" >
<Tag color={tag.color} name={tag.name} onEditClick={this.openEditTagModal.bind(this, tag.id, tag.name, tag.color)} onRemoveClick={this.onDeleteClick.bind(this, tag.id)} size='large' showEditButton showDeleteButton />
</div>
)
}
openTagModal() {
ModalContainer.openModal(
<AdminPanelCustomTagsModal onTagCreated={this.retrieveCustomTags.bind(this)} createTag />
);
}
openEditTagModal(tagId,tagName,tagColor, event) {
ModalContainer.openModal(
<AdminPanelCustomTagsModal defaultValues={{name: tagName , color: tagColor}} id={tagId} onTagChange={this.retrieveCustomTags.bind(this)}/>
);
}
onDeleteClick(tagId, event) {
event.preventDefault();
AreYouSure.openModal(i18n('WILL_DELETE_CUSTOM_RESPONSE'), this.deleteCustomTag.bind(this, tagId));
}
deleteCustomTag(tagId) {
API.call({
path: '/ticket/delete-tag',
data: {
tagId,
}
}).then(() => {
this.retrieveCustomTags()
});
}
retrieveCustomTags() {
this.props.dispatch(ConfigActions.updateData());
}
}
export default connect((store) => {
return {
tags: store.config['tags']
};
})(AdminPanelCustomTags);

View File

@ -0,0 +1,18 @@
.admin-panel-custom-tags {
&__content {
text-align: left;
}
&__add-button-icon{
margin-left: 5px;
}
&__tag-list{
margin-top: 15px;
}
&__tag-container{
margin-top:5px ;
}
}

View File

@ -26,13 +26,45 @@ class AdminPanelCustomTagsModal extends React.Component {
};
render() {
return (
this.props.createTag ? this.renderCreateTagContent() : this.renderEditTagContent()
);
}
renderEditTagContent() {
return (
<div className='admin-panel-custom-tags-modal'>
<Header title={i18n('EDIT_CUSTOM_TAG')} description={i18n('DESCRIPTION_EDIT_CUSTOM_TAG')} />
<Form
values={this.state.form}
onChange={this.onFormChange.bind(this)}
onSubmit={this.onSubmitEditTag.bind(this)}
errors={this.state.errors}
onValidateErrors={errors => this.setState({errors})}
loading={this.state.loading}>
<FormField name="name" label={i18n('NAME')} fieldProps={{size: 'large'}}/>
<FormField name="color" label={i18n('COLOR')} decorator={ColorSelector} />
<div className='admin-panel-custom-tags-modal__actions'>
<SubmitButton type="secondary" size="small">
{i18n('SAVE')}
</SubmitButton>
<Button onClick={this.onDiscardClick.bind(this)} size="small">
{i18n('CANCEL')}
</Button>
</div>
</Form>
</div>
);
}
renderCreateTagContent() {
return (
<div className='admin-panel-custom-tags-modal'>
<Header title={i18n('ADD_CUSTOM_TAG')} description={i18n('DESCRIPTION_ADD_CUSTOM_TAG')} />
<Form
values={this.state.form}
onChange={this.onFormChange.bind(this)}
onSubmit={this.onSubmitTag.bind(this)}
onSubmit={this.onSubmitNewTag.bind(this)}
errors={this.state.errors}
onValidateErrors={errors => this.setState({errors})}
loading={this.state.loading}>
@ -57,7 +89,40 @@ class AdminPanelCustomTagsModal extends React.Component {
});
}
onSubmitTag(form) {
onSubmitEditTag(form) {
this.setState({
loading: true
});
API.call({
path: '/ticket/edit-tag',
data: {
tagId: this.props.id,
name: form.name,
color: form.color,
}
}).then(() => {
this.context.closeModal();
this.setState({
loading: false,
errors: {}
});
if(this.props.onTagChange) {
this.props.onTagChange();
}
}).catch((result) => {
this.setState({
loading: false,
errors: {
'name': result.message
}
});
});
}
onSubmitNewTag(form) {
this.setState({
loading: true
});

View File

@ -55,7 +55,7 @@ class AdminPanelCustomTags extends React.Component {
}
renderTag(tag, index) {
return(
return (
<div key={index} className="admin-panel-custom-tags__tag-container" >
<Tag color={tag.color} name={tag.name} onRemoveClick={this.onDeleteClick.bind(this, tag.id)} size='large' showDeleteButton />
</div>

View File

@ -15,7 +15,8 @@ class Form extends React.Component {
onValidateErrors: React.PropTypes.func,
onChange: React.PropTypes.func,
values: React.PropTypes.object,
onSubmit: React.PropTypes.func
onSubmit: React.PropTypes.func,
defaultValues: React.PropTypes.object
};
static childContextTypes = {
@ -24,9 +25,8 @@ class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
form: {},
form: props.defaultValues || {},
validations: {},
errors: {}
};

View File

@ -16,11 +16,21 @@ class Tag extends React.Component {
return (
<div className={this.getClass()} style={{backgroundColor:this.props.color}} onClick={event => event.stopPropagation()} >
<span className="tag__name">{this.props.name}</span>
{this.props.showDeleteButton ? this.renderRemoveButton() : null}
<span>
{this.props.showEditButton ? this.renderEditButton() : null}
{this.props.showDeleteButton ? this.renderRemoveButton() : null}
</span>
</div>
);
}
renderEditButton() {
return (
<span onClick={this.props.onEditClick} className="tag__edit" >
<Icon name="pencil" size="small"/>
</span>
);
}
renderRemoveButton() {
return (
<span onClick={this.props.onRemoveClick} className="tag__remove" >

View File

@ -5,6 +5,7 @@
display: inline-block;
border-radius: 3px;
margin-left: 5px;
margin-top: 3px;
padding: 3px;
font-size: 13px;
cursor: default;
@ -17,6 +18,12 @@
color: $light-grey;
}
}
&__edit {
cursor: pointer;
&:hover {
color: $light-grey;
}
}
&_small {
font-size: 11px;

View File

@ -45,7 +45,7 @@ class TextEditor extends React.Component {
}
state = {
value: '',
value: this.props.value,
focused: false
};

View File

@ -106,6 +106,7 @@ export default {
'LAST_EDITED_IN': 'Last edited in {date}',
'EDIT': 'Edit',
'ADD_CUSTOM_TAG': 'Add custom tag',
'EDIT_CUSTOM_TAG': 'Edit custom tag',
'NO_RESULTS': 'No results',
'DELETE_AND_BAN': 'Delete and ban',
'STAFF_LEVEL': 'Staff Level',
@ -209,6 +210,7 @@ export default {
'OPTIONS': 'Options',
'FIELD_DESCRIPTION': 'Field description (Optional)',
'DESCRIPTION_ADD_CUSTOM_TAG': 'here you can add a new custom tag',
'DESCRIPTION_EDIT_CUSTOM_TAG': 'here you can edit a custom tag',
'CUSTOM_FIELDS': 'Custom fields',
'CHART_CREATE_TICKET': 'Tickets created',
@ -396,6 +398,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?',
'COMMENT_EDITED': '(comment edited)',
'LAST_7_DAYS': 'Last 7 days',
'LAST_30_DAYS': 'Last 30 days',
'LAST_90_DAYS': 'Last 90 days',

View File

@ -52,18 +52,18 @@ class DeleteStaffController extends Controller {
foreach($staff->sharedTicketList as $ticket) {
$ticket->owner = null;
$ticket->true = true;
$ticket->unreadStaff = true;
$ticket->store();
}
foreach($staff->sharedDepartmentList as $department) {
$department->owners--;
$department->store();
}
RedBean::exec('DELETE FROM log WHERE author_staff_id = ?', [$staffId]);
$staff->delete();
Response::respondSuccess();
}
}
}

View File

@ -30,7 +30,7 @@ class EmailPollingController extends Controller {
throw new RequestException(ERRORS::INVALID_TOKEN);
if(Controller::isUserSystemEnabled())
throw new RequestException(ERRORS::USER_SYSTEM);
throw new RequestException(ERRORS::USER_SYSTEM_ENABLED);
$this->mailbox = new \PhpImap\Mailbox(
Setting::getSetting('imap-host')->getValue(),

View File

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

View File

@ -95,7 +95,7 @@ class CommentController extends Controller {
'staff' => true
]);
} else if($isOwner) {
$this->sendMail($ticketAuthor);
!Controller::request('private') ? $this->sendMail($ticketAuthor) : null;
}
Log::createLog('COMMENT', $this->ticket->ticketNumber);
@ -151,12 +151,12 @@ class CommentController extends Controller {
}
$mailSender->setTemplate(MailTemplate::TICKET_RESPONDED, [
'to' => $email,
'name' => $name,
'title' => $this->ticket->title,
'ticketNumber' => $this->ticket->ticketNumber,
'content' => $this->content,
'url' => $url
'to' => $email,
'name' => $name,
'title' => $this->ticket->title,
'ticketNumber' => $this->ticket->ticketNumber,
'content' => $this->content,
'url' => $url
]);
$mailSender->send();

View File

@ -12,7 +12,7 @@ DataValidator::with('CustomValidations', true);
*
* @apiDescription This path creates a new tag.
*
* @apiPermission staff1
* @apiPermission staff3
*
* @apiParam {Number} name The new name of the tag.
* @apiParam {String} color The new color of the tag.
@ -31,7 +31,7 @@ class CreateTagController extends Controller {
public function validations() {
return [
'permission' => 'staff_1',
'permission' => 'staff_3',
'requestData' => [
'name' => [
'validation' => DataValidator::length(2, 100),

View File

@ -12,7 +12,7 @@ DataValidator::with('CustomValidations', true);
*
* @apiDescription This path delete a tag.
*
* @apiPermission staff1
* @apiPermission staff3
*
* @apiParam {Number} tagId The id of the tag.
*
@ -29,7 +29,7 @@ class DeleteTagController extends Controller {
public function validations() {
return [
'permission' => 'staff_1',
'permission' => 'staff_3',
'requestData' => [
'tagId' => [
'validation' => DataValidator::dataStoreId('tag'),

View File

@ -48,7 +48,7 @@ class DeleteController extends Controller {
throw new RequestException(ERRORS::NO_PERMISSION);
}
if(Controller::isStaffLogged() && $user->level < 3) {
if(Controller::isStaffLogged() && $user->level < 3 && ($user->email !== $ticketAuthor['email'])) {
throw new RequestException(ERRORS::NO_PERMISSION);
}

View File

@ -0,0 +1,68 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/edit-comment Edit a comment
* @apiVersion 4.4.0
*
* @apiName Edit comment
*
* @apiGroup Ticket
*
* @apiDescription This path edit a comment.
*
* @apiPermission user
*
* @apiParam {String} content The new content of the comment.
* @apiParam {Number} ticketEventId The id of the ticket event.
* @apiParam {Number} ticketNumber The id of the ticket number.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_CONENT
*
* @apiSuccess {Object} data Empty object
*
*/
class EditCommentController extends Controller {
const PATH = '/edit-comment';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'user',
'requestData' => [
'content' => [
'validation' => DataValidator::length(10, 5000),
'error' => ERRORS::INVALID_CONTENT
]
]
];
}
public function handler() {
$user = Controller::getLoggedUser();
$newcontent = Controller::request('content');
$ticketevent = Ticketevent::getTicketEvent(Controller::request('ticketEventId'));
$ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber'));
if(!Controller::isStaffLogged() && ($user->id !== $ticketevent->authorUserId && $user->id !== $ticket->authorId )){
throw new RequestException(ERRORS::NO_PERMISSION);
}
if(!$ticketevent->isNull()){
$ticketevent->content = $newcontent;
$ticketevent->editedContent = true;
$ticketevent->store();
}else{
$ticket->content = $newcontent;
$ticket->editedContent = true;
$ticket->store();
}
Response::respondSuccess();
}
}

View File

@ -12,7 +12,7 @@ DataValidator::with('CustomValidations', true);
*
* @apiDescription This path edit tags.
*
* @apiPermission staff1
* @apiPermission staff3
*
* @apiParam {Number} tagId The id of the tag.
* @apiParam {Number} name The new name of the tag.
@ -32,7 +32,7 @@ class EditTagController extends Controller {
public function validations() {
return [
'permission' => 'staff_1',
'permission' => 'staff_3',
'requestData' => [
'tagId' => [
'validation' => DataValidator::dataStoreId('tag'),

View File

@ -12,7 +12,7 @@ DataValidator::with('CustomValidations', true);
*
* @apiDescription This path returns all the tags.
*
* @apiPermission staff1
* @apiPermission staff3
*
* @apiUse NO_PERMISSION
*
@ -26,14 +26,14 @@ class GetTagsController extends Controller {
public function validations() {
return [
'permission' => 'staff_1',
'permission' => 'staff_3',
'requestData' => []
];
}
public function handler() {
$tags = Tag::getAll();
Response::respondSuccess($tags->toArray());
}
}

View File

@ -50,7 +50,8 @@ class Ticket extends DataStore {
'language',
'authorEmail',
'authorName',
'sharedTagList'
'sharedTagList',
'editedContent'
);
}
@ -130,7 +131,8 @@ class Ticket extends DataStore {
'author' => $this->authorToArray(),
'owner' => $this->ownerToArray(),
'events' => $minimized ? [] : $this->eventsToArray(),
'tags' => $this->sharedTagList->toArray(true)
'tags' => $this->sharedTagList->toArray(true),
'edited' => $this->editedContent
];
}
@ -181,6 +183,8 @@ class Ticket extends DataStore {
'date'=> $ticketEvent->date,
'file'=> $ticketEvent->file,
'private'=> $ticketEvent->private,
'edited' => $ticketEvent->editedContent,
'id' => $ticketEvent->id
];
$author = $ticketEvent->getAuthor();

View File

@ -60,7 +60,8 @@ class Ticketevent extends DataStore {
'authorUser',
'authorStaff',
'date',
'private'
'private',
'editedContent'
];
}
@ -75,6 +76,10 @@ class Ticketevent extends DataStore {
return new NullDataStore();
}
public static function getTicketEvent($value, $property = 'id') {
return parent::getDataStore($value, $property);
}
public function toArray() {
$user = ($this->authorStaff) ? $this->authorStaff : $this->authorUser;
@ -87,7 +92,8 @@ class Ticketevent extends DataStore {
'staff' => $user instanceOf Staff,
'id' => $user ? $user->id : null,
'customfields' => $user->xownCustomfieldvalueList ? $user->xownCustomfieldvalueList->toArray() : [],
]
],
'edited' => $this->editedContent
];
}
}

View File

@ -68,5 +68,6 @@ require './ticket/get-tags.rb'
require './ticket/delete-tag.rb'
require './ticket/add-tag.rb'
require './ticket/delete-tag.rb'
require './ticket/edit-comment.rb'
require './system/disable-user-system.rb'
# require './system/get-stats.rb'

View File

@ -67,10 +67,10 @@ class Scripts
request('/user/logout')
end
def self.createTicket(title = 'Winter is coming')
def self.createTicket(title = 'Winter is coming',content = 'The north remembers')
result = request('/ticket/create', {
title: title,
content: 'The north remembers',
content: content,
departmentId: 1,
language: 'en',
csrf_userid: $csrf_userid,
@ -115,4 +115,19 @@ class Scripts
color: color
})
end
def self.assignTicket(ticketnumber)
request('/staff/assign-ticket', {
ticketNumber: ticketnumber,
csrf_userid: $csrf_userid,
csrf_token: $csrf_token
})
end
def self.commentTicket(ticketnumber,content)
request('/ticket/comment', {
content: content,
ticketNumber: ticketnumber,
csrf_userid: $csrf_userid,
csrf_token: $csrf_token
})
end
end

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(40)
(numberOftickets.num_rows).should.equal(41)
request('/user/logout')
@ -127,7 +127,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(41)
(numberOftickets.num_rows).should.equal(42)
end

View File

@ -0,0 +1,97 @@
describe '/ticket/edit-comment' do
request('/user/logout')
Scripts.login();
Scripts.createTicket('ticket made by an user','content of the ticket made by an user')
ticket = $database.getRow('ticket', 'ticket made by an user', 'title')
Scripts.commentTicket(ticket['ticket_number'],'com ment of a user')
it 'should change content of the ticket if the author user tries it' do
result = request('/ticket/edit-comment', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
content: 'content edited by the user',
ticketNumber: ticket['ticket_number']
})
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
it 'should change the content of a comment if the user is the author' do
ticketevent = $database.getRow('ticketevent', 'com ment of a user', 'content')
result = request('/ticket/edit-comment', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
content: 'comment edited by the user',
ticketEventId: ticketevent['id']
})
ticketevent = $database.getRow('ticketevent', 'comment edited by the user', 'content')
(result['status']).should.equal('success')
(ticketevent['content']).should.equal('comment edited by the user')
end
it 'should change the content of a comment and the content of the ticket if the admin is logged' do
request('/user/logout')
Scripts.login($staff[:email], $staff[:password], true)
ticketevent = $database.getRow('ticketevent', 'comment edited by the user', 'content')
result = request('/ticket/edit-comment', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
content: 'comment edited by a staff',
ticketEventId: ticketevent['id']
})
ticketevent = $database.getRow('ticketevent', 'comment edited by a staff', 'content')
(result['status']).should.equal('success')
(ticketevent['content']).should.equal('comment edited by a staff')
result = request('/ticket/edit-comment', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
content: 'content edited by a staff',
ticketNumber: ticket['ticket_number']
})
ticket = $database.getRow('ticket', ticket['ticket_number'], 'ticket_number')
(result['status']).should.equal('success')
(ticket['content']).should.equal('content edited by a staff')
request('/user/logout')
end
it 'should not change the content of a comment if the user is not the author' do
request('/user/logout')
Scripts.login($staff[:email], $staff[:password], true)
ticket = $database.getRow('ticket', 'ticket made by an user', 'title')
Scripts.assignTicket(ticket['ticket_number'])
Scripts.commentTicket(ticket['ticket_number'],'this is a new comment of a staff member')
ticketevent = $database.getRow('ticketevent', 'this is a new comment of a staff member', 'content')
request('/user/logout')
Scripts.login();
result = request('/ticket/edit-comment', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
content: 'comment edited by an user',
ticketEventId: ticketevent['id']
})
(result['status']).should.equal('fail')
(result['message']).should.equal('NO_PERMISSION')
end
end