mirror of
https://github.com/opensupports/opensupports.git
synced 2025-07-28 16:24:42 +02:00
Merge pull request #479 from ivandiazwm/guillermo-master
Guillermo master
This commit is contained in:
commit
a355fc30da
@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import {connect} from "react-redux";
|
||||||
|
|
||||||
import i18n from 'lib-app/i18n';
|
import i18n from 'lib-app/i18n';
|
||||||
import DateTransformer from 'lib-core/date-transformer';
|
import DateTransformer from 'lib-core/date-transformer';
|
||||||
@ -11,6 +12,7 @@ import Button from 'core-components/button';
|
|||||||
import Tooltip from 'core-components/tooltip';
|
import Tooltip from 'core-components/tooltip';
|
||||||
import Icon from 'core-components/icon';
|
import Icon from 'core-components/icon';
|
||||||
import Checkbox from 'core-components/checkbox';
|
import Checkbox from 'core-components/checkbox';
|
||||||
|
import Tag from 'core-components/tag';
|
||||||
|
|
||||||
class TicketList extends React.Component {
|
class TicketList extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -182,9 +184,16 @@ class TicketList extends React.Component {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
),
|
),
|
||||||
title: (
|
title: (
|
||||||
|
<div>
|
||||||
<Button className="ticket-list__title-link" type="clean" route={{to: this.props.ticketPath + ticket.ticketNumber}}>
|
<Button className="ticket-list__title-link" type="clean" route={{to: this.props.ticketPath + ticket.ticketNumber}}>
|
||||||
{titleText}
|
{titleText}
|
||||||
</Button>
|
</Button>
|
||||||
|
{ticket.tags.map((tagName,index) => {
|
||||||
|
let tag = _.find(this.props.tags, {name:tagName});
|
||||||
|
return <Tag size='small' name={tag && tag.name} color={tag && tag.color} key={index} />
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
),
|
),
|
||||||
priority: this.getTicketPriority(ticket.priority),
|
priority: this.getTicketPriority(ticket.priority),
|
||||||
department: ticket.department.name,
|
department: ticket.department.name,
|
||||||
@ -257,5 +266,8 @@ class TicketList extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default connect((store) => {
|
||||||
export default TicketList;
|
return {
|
||||||
|
tags: store.config['tags']
|
||||||
|
};
|
||||||
|
})(TicketList);
|
@ -22,6 +22,8 @@ import Icon from 'core-components/icon';
|
|||||||
import TextEditor from 'core-components/text-editor';
|
import TextEditor from 'core-components/text-editor';
|
||||||
import InfoTooltip from 'core-components/info-tooltip';
|
import InfoTooltip from 'core-components/info-tooltip';
|
||||||
import DepartmentDropdown from 'app-components/department-dropdown';
|
import DepartmentDropdown from 'app-components/department-dropdown';
|
||||||
|
import TagSelector from 'core-components/tag-selector';
|
||||||
|
import Tag from 'core-components/tag';
|
||||||
|
|
||||||
class TicketViewer extends React.Component {
|
class TicketViewer extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -36,10 +38,12 @@ class TicketViewer extends React.Component {
|
|||||||
userId: React.PropTypes.number,
|
userId: React.PropTypes.number,
|
||||||
userStaff: React.PropTypes.bool,
|
userStaff: React.PropTypes.bool,
|
||||||
userDepartments: React.PropTypes.array,
|
userDepartments: React.PropTypes.array,
|
||||||
userLevel: React.PropTypes.number
|
userLevel: React.PropTypes.number,
|
||||||
|
tags: React.PropTypes.array
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
tags: [],
|
||||||
editable: false,
|
editable: false,
|
||||||
ticket: {
|
ticket: {
|
||||||
author: {},
|
author: {},
|
||||||
@ -63,6 +67,7 @@ class TicketViewer extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const ticket = this.props.ticket;
|
const ticket = this.props.ticket;
|
||||||
|
console.log('tickett',ticket)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ticket-viewer">
|
<div className="ticket-viewer">
|
||||||
@ -105,7 +110,7 @@ class TicketViewer extends React.Component {
|
|||||||
<div className="ticket-viewer__info-row-header row">
|
<div className="ticket-viewer__info-row-header row">
|
||||||
<div className="col-md-4">{i18n('DEPARTMENT')}</div>
|
<div className="col-md-4">{i18n('DEPARTMENT')}</div>
|
||||||
<div className="col-md-4">{i18n('AUTHOR')}</div>
|
<div className="col-md-4">{i18n('AUTHOR')}</div>
|
||||||
<div className="col-md-4">{i18n('DATE')}</div>
|
<div className="col-md-4">{i18n('TAGS')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ticket-viewer__info-row-values row">
|
<div className="ticket-viewer__info-row-values row">
|
||||||
<div className="col-md-4">
|
<div className="col-md-4">
|
||||||
@ -115,7 +120,7 @@ class TicketViewer extends React.Component {
|
|||||||
onChange={this.onDepartmentDropdownChanged.bind(this)} />
|
onChange={this.onDepartmentDropdownChanged.bind(this)} />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-4">{ticket.author.name}</div>
|
<div className="col-md-4">{ticket.author.name}</div>
|
||||||
<div className="col-md-4">{DateTransformer.transformToString(ticket.date)}</div>
|
<div className="col-md-4"> <TagSelector items={this.props.tags} values={this.props.ticket.tags} onRemoveClick={this.removeTag.bind(this)} onTagSelected={this.addTag.bind(this)}/></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ticket-viewer__info-row-header row">
|
<div className="ticket-viewer__info-row-header row">
|
||||||
<div className="col-md-4">{i18n('PRIORITY')}</div>
|
<div className="col-md-4">{i18n('PRIORITY')}</div>
|
||||||
@ -153,12 +158,15 @@ class TicketViewer extends React.Component {
|
|||||||
<div className="ticket-viewer__info-row-header row">
|
<div className="ticket-viewer__info-row-header row">
|
||||||
<div className="ticket-viewer__department col-md-4">{i18n('DEPARTMENT')}</div>
|
<div className="ticket-viewer__department col-md-4">{i18n('DEPARTMENT')}</div>
|
||||||
<div className="ticket-viewer__author col-md-4">{i18n('AUTHOR')}</div>
|
<div className="ticket-viewer__author col-md-4">{i18n('AUTHOR')}</div>
|
||||||
<div className="ticket-viewer__date col-md-4">{i18n('DATE')}</div>
|
<div className="ticket-viewer__date col-md-4">{i18n('TAGS')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ticket-viewer__info-row-values row">
|
<div className="ticket-viewer__info-row-values row">
|
||||||
<div className="ticket-viewer__department col-md-4">{ticket.department.name}</div>
|
<div className="ticket-viewer__department col-md-4">{ticket.department.name}</div>
|
||||||
<div className="ticket-viewer__author col-md-4">{ticket.author.name}</div>
|
<div className="ticket-viewer__author col-md-4">{ticket.author.name}</div>
|
||||||
<div className="ticket-viewer__date col-md-4">{DateTransformer.transformToString(ticket.date, false)}</div>
|
<div className="col-md-4">{ticket.tags.length ? ticket.tags.map((tagName,index) => {
|
||||||
|
let tag = _.find(this.props.tags, {name:tagName});
|
||||||
|
return <Tag name={tag && tag.name} color={tag && tag.color} key={index} />
|
||||||
|
}) : i18n('NONE')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ticket-viewer__info-row-header row">
|
<div className="ticket-viewer__info-row-header row">
|
||||||
<div className="ticket-viewer__department col-md-4">{i18n('PRIORITY')}</div>
|
<div className="ticket-viewer__department col-md-4">{i18n('PRIORITY')}</div>
|
||||||
@ -412,7 +420,25 @@ class TicketViewer extends React.Component {
|
|||||||
}
|
}
|
||||||
}).then(this.onTicketModification.bind(this));
|
}).then(this.onTicketModification.bind(this));
|
||||||
}
|
}
|
||||||
|
addTag(tag) {
|
||||||
|
API.call({
|
||||||
|
path: '/ticket/add-tag',
|
||||||
|
data: {
|
||||||
|
ticketNumber: this.props.ticket.ticketNumber,
|
||||||
|
tagId: tag
|
||||||
|
}
|
||||||
|
}).then(this.onTicketModification.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTag(tag) {
|
||||||
|
API.call({
|
||||||
|
path: '/ticket/remove-tag',
|
||||||
|
data: {
|
||||||
|
ticketNumber: this.props.ticket.ticketNumber,
|
||||||
|
tagId: tag
|
||||||
|
}
|
||||||
|
}).then(this.onTicketModification.bind(this))
|
||||||
|
}
|
||||||
onCustomResponsesChanged({index}) {
|
onCustomResponsesChanged({index}) {
|
||||||
let replaceContentWithCustomResponse = () => {
|
let replaceContentWithCustomResponse = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -515,6 +541,7 @@ export default connect((store) => {
|
|||||||
staffMembersLoaded: store.adminData.staffMembersLoaded,
|
staffMembersLoaded: store.adminData.staffMembersLoaded,
|
||||||
allowAttachments: store.config['allow-attachments'],
|
allowAttachments: store.config['allow-attachments'],
|
||||||
userSystemEnabled: store.config['user-system-enabled'],
|
userSystemEnabled: store.config['user-system-enabled'],
|
||||||
userLevel: store.session.userLevel
|
userLevel: store.session.userLevel,
|
||||||
|
tags: store.config['tags']
|
||||||
};
|
};
|
||||||
})(TicketViewer);
|
})(TicketViewer);
|
||||||
|
@ -35,6 +35,7 @@ import AdminPanelNewTickets from 'app/admin/panel/tickets/admin-panel-new-ticket
|
|||||||
import AdminPanelAllTickets from 'app/admin/panel/tickets/admin-panel-all-tickets';
|
import AdminPanelAllTickets from 'app/admin/panel/tickets/admin-panel-all-tickets';
|
||||||
import AdminPanelViewTicket from 'app/admin/panel/tickets/admin-panel-view-ticket';
|
import AdminPanelViewTicket from 'app/admin/panel/tickets/admin-panel-view-ticket';
|
||||||
import AdminPanelCustomResponses from 'app/admin/panel/tickets/admin-panel-custom-responses';
|
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 AdminPanelListUsers from 'app/admin/panel/users/admin-panel-list-users';
|
||||||
import AdminPanelViewUser from 'app/admin/panel/users/admin-panel-view-user';
|
import AdminPanelViewUser from 'app/admin/panel/users/admin-panel-view-user';
|
||||||
@ -114,6 +115,7 @@ export default (
|
|||||||
<Route path="all-tickets" component={AdminPanelAllTickets} />
|
<Route path="all-tickets" component={AdminPanelAllTickets} />
|
||||||
<Route path="custom-responses" component={AdminPanelCustomResponses} />
|
<Route path="custom-responses" component={AdminPanelCustomResponses} />
|
||||||
<Route path="view-ticket/:ticketNumber" component={AdminPanelViewTicket} />
|
<Route path="view-ticket/:ticketNumber" component={AdminPanelViewTicket} />
|
||||||
|
<Route path="custom-tags" component={AdminPanelCustomTags} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path="users">
|
<Route path="users">
|
||||||
|
@ -135,6 +135,11 @@ class AdminPanelMenu extends React.Component {
|
|||||||
name: i18n('CUSTOM_RESPONSES'),
|
name: i18n('CUSTOM_RESPONSES'),
|
||||||
path: '/admin/panel/tickets/custom-responses',
|
path: '/admin/panel/tickets/custom-responses',
|
||||||
level: 2
|
level: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n('CUSTOM_TAGS'),
|
||||||
|
path: '/admin/panel/tickets/custom-tags',
|
||||||
|
level: 1
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,104 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import i18n from 'lib-app/i18n';
|
||||||
|
import API from 'lib-app/api-call';
|
||||||
|
|
||||||
|
import Button from 'core-components/button';
|
||||||
|
import Header from 'core-components/header';
|
||||||
|
import Form from 'core-components/form';
|
||||||
|
import FormField from 'core-components/form-field';
|
||||||
|
import SubmitButton from 'core-components/submit-button';
|
||||||
|
import ColorSelector from 'core-components/color-selector';
|
||||||
|
|
||||||
|
class AdminPanelCustomTagsModal extends React.Component {
|
||||||
|
static contextTypes = {
|
||||||
|
closeModal: React.PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
defaultValues: React.PropTypes.object,
|
||||||
|
onTagCreated: React.PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
form: this.props.defaultValues || {name: '', color: '#ff6900'},
|
||||||
|
loading: false
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
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)}
|
||||||
|
errors={this.state.errors}
|
||||||
|
onValidateErrors={errors => this.setState({errors})}
|
||||||
|
loading={this.state.loading}>
|
||||||
|
<FormField name="name" label={i18n('NAME')} fieldProps={{size: 'large'}} required />
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onFormChange(form) {
|
||||||
|
this.setState({
|
||||||
|
form
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmitTag(form) {
|
||||||
|
this.setState({
|
||||||
|
loading: true
|
||||||
|
});
|
||||||
|
|
||||||
|
API.call({
|
||||||
|
path: '/ticket/create-tag',
|
||||||
|
data: {
|
||||||
|
name: form.name,
|
||||||
|
color: form.color,
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
this.context.closeModal();
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errors: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(this.props.onTagCreated) {
|
||||||
|
this.props.onTagCreated();
|
||||||
|
}
|
||||||
|
|
||||||
|
}).catch((result) => {
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errors: {
|
||||||
|
'name': result.message
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onDiscardClick(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.context.closeModal();
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errors: {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AdminPanelCustomTagsModal;
|
@ -0,0 +1,7 @@
|
|||||||
|
.admin-panel-custom-tags-modal {
|
||||||
|
|
||||||
|
&__actions{
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
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('CREATE_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} onRemoveClick={this.onDeleteClick.bind(this, tag.id)} size='large' showDeleteButton />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
openTagModal() {
|
||||||
|
ModalContainer.openModal(
|
||||||
|
<AdminPanelCustomTagsModal onTagCreated={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);
|
@ -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 ;
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@ const Menu = require('core-components/menu');
|
|||||||
const Tooltip = require('core-components/tooltip');
|
const Tooltip = require('core-components/tooltip');
|
||||||
const Table = require('core-components/table');
|
const Table = require('core-components/table');
|
||||||
const InfoTooltip = require('core-components/info-tooltip');
|
const InfoTooltip = require('core-components/info-tooltip');
|
||||||
|
const TagSelector = require('core-components/tag-selector');
|
||||||
|
|
||||||
function rand(min, max, num) {
|
function rand(min, max, num) {
|
||||||
var rtn = [];
|
var rtn = [];
|
||||||
@ -75,6 +76,23 @@ let DemoPage = React.createClass({
|
|||||||
<Button type="primary">Sign up</Button>
|
<Button type="primary">Sign up</Button>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Tag selector',
|
||||||
|
render: (
|
||||||
|
<TagSelector
|
||||||
|
items={[
|
||||||
|
{name: 'tag1', color: 'blue'},
|
||||||
|
{name: 'suggestion', color: '#ff6900'},
|
||||||
|
{name: 'tag3', color: 'red'},
|
||||||
|
{name: 'tag4', color: 'green'},
|
||||||
|
{name: 'bug', color: '#eb144c'},
|
||||||
|
]}
|
||||||
|
values={['suggestion','bug', 'tag4']}
|
||||||
|
onRemoveClick={(e) => console.log('deleted click', e)}
|
||||||
|
onTagSelected={(e) => console.log('selected click', e)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Input',
|
title: 'Input',
|
||||||
render: (
|
render: (
|
||||||
|
@ -50,11 +50,12 @@ class DropDown extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
let animation = this.getAnimationStyles();
|
let animation = this.getAnimationStyles();
|
||||||
let selectedItem = this.props.items[this.getSelectedIndex()];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={this.getClass()}>
|
<div className={this.getClass()}>
|
||||||
{this.renderCurrentItem(selectedItem)}
|
<div {...this.getCurrentItemProps()}>
|
||||||
|
{this.props.children ? this.props.children : this.renderCurrentItem()}
|
||||||
|
</div>
|
||||||
<Motion defaultStyle={animation.defaultStyle} style={animation.style} onRest={this.onAnimationFinished.bind(this)}>
|
<Motion defaultStyle={animation.defaultStyle} style={animation.style} onRest={this.onAnimationFinished.bind(this)}>
|
||||||
{this.renderList.bind(this)}
|
{this.renderList.bind(this)}
|
||||||
</Motion>
|
</Motion>
|
||||||
@ -72,15 +73,17 @@ class DropDown extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCurrentItem(item) {
|
renderCurrentItem() {
|
||||||
var iconNode = null;
|
let item = this.props.items[this.getSelectedIndex()];
|
||||||
|
let iconNode = null;
|
||||||
|
|
||||||
|
|
||||||
if (item.icon) {
|
if (item.icon) {
|
||||||
iconNode = <Icon className="drop-down__current-item-icon" name={item.icon} />;
|
iconNode = <Icon className="drop-down__current-item-icon" name={item.icon} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div {...this.getCurrentItemProps()}>
|
<div>
|
||||||
{iconNode}{item.content}
|
{iconNode}{item.content}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
67
client/src/core-components/tag-selector.js
Normal file
67
client/src/core-components/tag-selector.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import Icon from 'core-components/icon';
|
||||||
|
import DropDown from 'core-components/drop-down';
|
||||||
|
import Tag from 'core-components/tag';
|
||||||
|
|
||||||
|
class TagSelector extends React.Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
items: React.PropTypes.arrayOf(React.PropTypes.shape({
|
||||||
|
name: React.PropTypes.string,
|
||||||
|
color: React.PropTypes.string,
|
||||||
|
})),
|
||||||
|
values: React.PropTypes.arrayOf(React.PropTypes.string),
|
||||||
|
onRemoveClick: React.PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="tag-selector">
|
||||||
|
<DropDown className="tag-selector__drop-down" items={this.renderTagOptions().map(tag => ({content: tag}))} selectedIndex={-1} size="large">
|
||||||
|
{this.renderSelectedTags()}
|
||||||
|
</DropDown>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSelectedTags() {
|
||||||
|
const itemList = this.props.values.map(value => _.find(this.props.items, {name:value}));
|
||||||
|
|
||||||
|
return itemList.map(this.renderSelectedTag.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
renderSelectedTag(item,index) {
|
||||||
|
return <Tag name={item.name} color={item.color} showDeleteButton onRemoveClick={this.onRemoveClick.bind(this,item.id)} key={index}/>;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTagOptions() {
|
||||||
|
const itemList = _.filter(this.props.items,(item) => !_.includes(this.props.values,item.name));
|
||||||
|
|
||||||
|
return itemList.map(this.renderTagOption.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTagOption(item,index) {
|
||||||
|
return (
|
||||||
|
<div onClick={this.onTagSelected.bind(this,item.id)} className="tag-selector__tag-option" key={index}>
|
||||||
|
<span className="tag-selector__tag-option-square" style={{backgroundColor:item.color}}/>
|
||||||
|
<span className="tag-selector__tag-option-name" >{item.name}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onRemoveClick(tagId) {
|
||||||
|
if(this.props.onRemoveClick){
|
||||||
|
this.props.onRemoveClick(tagId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onTagSelected(tagId) {
|
||||||
|
if(this.props.onTagSelected){
|
||||||
|
this.props.onTagSelected(tagId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default TagSelector;
|
42
client/src/core-components/tag-selector.scss
Normal file
42
client/src/core-components/tag-selector.scss
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
@import '../scss/vars';
|
||||||
|
|
||||||
|
.tag-selector{
|
||||||
|
margin-bottom: 30px;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&__drop-down {
|
||||||
|
.drop-down__current-item {
|
||||||
|
cursor: text;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid $grey;
|
||||||
|
min-height: 38px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: $primary-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tag-options {
|
||||||
|
font-size: 13px;
|
||||||
|
color: $dark-grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tag-option {
|
||||||
|
color: $primary-black;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&-square {
|
||||||
|
display: inline-block;
|
||||||
|
height: 15px;
|
||||||
|
width: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-name {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
client/src/core-components/tag.js
Normal file
43
client/src/core-components/tag.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Icon from 'core-components/icon';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
class Tag extends React.Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
name: React.PropTypes.string,
|
||||||
|
color: React.PropTypes.string,
|
||||||
|
showDeleteButton: React.PropTypes.bool,
|
||||||
|
onRemoveClick: React.PropTypes.func,
|
||||||
|
size: React.PropTypes.oneOf(['small','medium','large'])
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
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}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderRemoveButton() {
|
||||||
|
return (
|
||||||
|
<span onClick={this.props.onRemoveClick} className="tag__remove" >
|
||||||
|
<Icon name="times-circle" size="small"/>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getClass() {
|
||||||
|
let classes = {
|
||||||
|
'tag': true,
|
||||||
|
'tag_small': this.props.size === 'small',
|
||||||
|
'tag_medium': this.props.size === 'medium',
|
||||||
|
'tag_large': this.props.size === 'large',
|
||||||
|
};
|
||||||
|
|
||||||
|
return classNames(classes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default Tag;
|
31
client/src/core-components/tag.scss
Normal file
31
client/src/core-components/tag.scss
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
@import '../scss/vars';
|
||||||
|
|
||||||
|
.tag{
|
||||||
|
color: white;
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-left: 5px;
|
||||||
|
padding: 3px;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
&__remove {
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 10px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $light-grey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&_small {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_large {
|
||||||
|
width: 220px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 3px 8px;
|
||||||
|
}
|
||||||
|
}
|
@ -30,7 +30,8 @@ module.exports = [
|
|||||||
'allowedLanguages': ['en', 'es', 'de', 'fr', 'pt', 'jp', 'ru', 'cn', 'in', 'tr'],
|
'allowedLanguages': ['en', 'es', 'de', 'fr', 'pt', 'jp', 'ru', 'cn', 'in', 'tr'],
|
||||||
'supportedLanguages': ['en', 'es', 'de'],
|
'supportedLanguages': ['en', 'es', 'de'],
|
||||||
'registration': true,
|
'registration': true,
|
||||||
'user-system-enabled': true
|
'user-system-enabled': true,
|
||||||
|
'tags': [{id:1,name:'bug', color:'#eb144c'},{id: 2,name:'suggestion',color:'#ff6900'}]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -51,7 +52,8 @@ module.exports = [
|
|||||||
'allowedLanguages': ['en', 'es', 'de', 'fr', 'pt', 'jp', 'ru', 'cn', 'in', 'tr'],
|
'allowedLanguages': ['en', 'es', 'de', 'fr', 'pt', 'jp', 'ru', 'cn', 'in', 'tr'],
|
||||||
'supportedLanguages': ['en', 'es', 'de'],
|
'supportedLanguages': ['en', 'es', 'de'],
|
||||||
'registration': true,
|
'registration': true,
|
||||||
'user-system-enabled': true
|
'user-system-enabled': true,
|
||||||
|
'tags': [{id:1,name:'bug', color:'#eb144c'},{id: 2,name:'suggestion',color:'#ff6900'}]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -109,6 +109,7 @@ module.exports = [
|
|||||||
unreadStaff: true,
|
unreadStaff: true,
|
||||||
closed: false,
|
closed: false,
|
||||||
priority: 'medium',
|
priority: 'medium',
|
||||||
|
tags: [],
|
||||||
author: {
|
author: {
|
||||||
id: '3',
|
id: '3',
|
||||||
name: 'Haskell Curry',
|
name: 'Haskell Curry',
|
||||||
|
@ -47,6 +47,8 @@ export default {
|
|||||||
'NEW_TICKETS': 'New Tickets',
|
'NEW_TICKETS': 'New Tickets',
|
||||||
'ALL_TICKETS': 'All Tickets',
|
'ALL_TICKETS': 'All Tickets',
|
||||||
'CUSTOM_RESPONSES': 'Custom Responses',
|
'CUSTOM_RESPONSES': 'Custom Responses',
|
||||||
|
'CUSTOM_TAGS': 'Custom Tags',
|
||||||
|
'CREATE_CUSTOM_TAG': 'Create custom tag',
|
||||||
'LIST_USERS': 'List Users',
|
'LIST_USERS': 'List Users',
|
||||||
'BAN_USERS': 'Ban Users',
|
'BAN_USERS': 'Ban Users',
|
||||||
'LIST_ARTICLES': 'Article List',
|
'LIST_ARTICLES': 'Article List',
|
||||||
@ -102,6 +104,7 @@ export default {
|
|||||||
'COLOR': 'Color',
|
'COLOR': 'Color',
|
||||||
'ADD_NEW_ARTICLE': 'Add new article',
|
'ADD_NEW_ARTICLE': 'Add new article',
|
||||||
'ADD_ARTICLE': 'Add article',
|
'ADD_ARTICLE': 'Add article',
|
||||||
|
'ADD_CUSTOM_TAG': 'Add custom tag',
|
||||||
'LAST_EDITED_IN': 'Last edited in {date}',
|
'LAST_EDITED_IN': 'Last edited in {date}',
|
||||||
'EDIT': 'Edit',
|
'EDIT': 'Edit',
|
||||||
'NO_RESULTS': 'No results',
|
'NO_RESULTS': 'No results',
|
||||||
@ -206,6 +209,7 @@ export default {
|
|||||||
'OPTION': 'Option {index}',
|
'OPTION': 'Option {index}',
|
||||||
'OPTIONS': 'Options',
|
'OPTIONS': 'Options',
|
||||||
'FIELD_DESCRIPTION': 'Field description (Optional)',
|
'FIELD_DESCRIPTION': 'Field description (Optional)',
|
||||||
|
'DESCRIPTION_ADD_CUSTOM_TAG': 'here you can add a new custom tag',
|
||||||
'CUSTOM_FIELDS': 'Custom fields',
|
'CUSTOM_FIELDS': 'Custom fields',
|
||||||
|
|
||||||
'CHART_CREATE_TICKET': 'Tickets created',
|
'CHART_CREATE_TICKET': 'Tickets created',
|
||||||
@ -283,6 +287,7 @@ export default {
|
|||||||
'ACCOUNT_DESCRIPTION': 'All your tickets are stored in your account\'s profile. Keep track of all your tickets you send to our staff team.',
|
'ACCOUNT_DESCRIPTION': 'All your tickets are stored in your account\'s profile. Keep track of all your tickets you send to our staff team.',
|
||||||
'SUPPORT_CENTER_DESCRIPTION': 'Welcome to our support center. You can contact us through a tickets system. Your tickets will be answered by our staff.',
|
'SUPPORT_CENTER_DESCRIPTION': 'Welcome to our support center. You can contact us through a tickets system. Your tickets will be answered by our staff.',
|
||||||
'CUSTOM_RESPONSES_DESCRIPTION': 'Custom responses are automated responses for common problems',
|
'CUSTOM_RESPONSES_DESCRIPTION': 'Custom responses are automated responses for common problems',
|
||||||
|
'CUSTOM_TAGS_DESCRIPTION': 'Here you can view manage the custom tags for tickets to identify them better',
|
||||||
'MY_TICKETS_DESCRIPTION': 'Here you can view the tickets you are responsible for.',
|
'MY_TICKETS_DESCRIPTION': 'Here you can view the tickets you are responsible for.',
|
||||||
'NEW_TICKETS_DESCRIPTION': 'Here you can view all the new tickets that are not assigned by anyone.',
|
'NEW_TICKETS_DESCRIPTION': 'Here you can view all the new tickets that are not assigned by anyone.',
|
||||||
'ALL_TICKETS_DESCRIPTION': 'Here you can view the tickets of the departments you are assigned.',
|
'ALL_TICKETS_DESCRIPTION': 'Here you can view the tickets of the departments you are assigned.',
|
||||||
|
@ -62,6 +62,8 @@ class SessionStore {
|
|||||||
this.setItem('allow-attachments', configs['allow-attachments']);
|
this.setItem('allow-attachments', configs['allow-attachments']);
|
||||||
this.setItem('maintenance-mode', configs['maintenance-mode']);
|
this.setItem('maintenance-mode', configs['maintenance-mode']);
|
||||||
this.setItem('max-size', configs['max-size']);
|
this.setItem('max-size', configs['max-size']);
|
||||||
|
this.setItem('tags', JSON.stringify(configs['tags']));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getConfigs() {
|
getConfigs() {
|
||||||
@ -78,6 +80,7 @@ class SessionStore {
|
|||||||
'allow-attachments': (this.getItem('allow-attachments') * 1),
|
'allow-attachments': (this.getItem('allow-attachments') * 1),
|
||||||
'maintenance-mode': (this.getItem('maintenance-mode') * 1),
|
'maintenance-mode': (this.getItem('maintenance-mode') * 1),
|
||||||
'max-size': this.getItem('max-size'),
|
'max-size': this.getItem('max-size'),
|
||||||
|
'tags': JSON.parse(this.getItem('tags'))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,8 @@ use Respect\Validation\Validator as DataValidator;
|
|||||||
* @apiParam {Number} name Name of the custom field.
|
* @apiParam {Number} name Name of the custom field.
|
||||||
* @apiParam {String} type One of 'text' and 'select'.
|
* @apiParam {String} type One of 'text' and 'select'.
|
||||||
* @apiParam {String} options JSON array of strings with the option names.
|
* @apiParam {String} options JSON array of strings with the option names.
|
||||||
*
|
* @apiParam {String} description Description of the custom field.
|
||||||
|
|
||||||
* @apiUse NO_PERMISSION
|
* @apiUse NO_PERMISSION
|
||||||
* @apiUse INVALID_NAME
|
* @apiUse INVALID_NAME
|
||||||
* @apiUse INVALID_CUSTOM_FIELD_TYPE
|
* @apiUse INVALID_CUSTOM_FIELD_TYPE
|
||||||
|
@ -56,7 +56,8 @@ class GetSettingsController extends Controller {
|
|||||||
'supportedLanguages' => Language::getSupportedLanguages(),
|
'supportedLanguages' => Language::getSupportedLanguages(),
|
||||||
'allowedLanguages' => Language::getAllowedLanguages(),
|
'allowedLanguages' => Language::getAllowedLanguages(),
|
||||||
'session-prefix' => Setting::getSetting('session-prefix')->getValue(),
|
'session-prefix' => Setting::getSetting('session-prefix')->getValue(),
|
||||||
'mail-template-header-image' => Setting::getSetting('mail-template-header-image')->getValue()
|
'mail-template-header-image' => Setting::getSetting('mail-template-header-image')->getValue(),
|
||||||
|
'tags' => Tag::getAll()->toArray()
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
$settingsList = [
|
$settingsList = [
|
||||||
@ -73,7 +74,8 @@ class GetSettingsController extends Controller {
|
|||||||
'supportedLanguages' => Language::getSupportedLanguages(),
|
'supportedLanguages' => Language::getSupportedLanguages(),
|
||||||
'allowedLanguages' => Language::getAllowedLanguages(),
|
'allowedLanguages' => Language::getAllowedLanguages(),
|
||||||
'user-system-enabled' => intval(Setting::getSetting('user-system-enabled')->getValue()),
|
'user-system-enabled' => intval(Setting::getSetting('user-system-enabled')->getValue()),
|
||||||
'session-prefix' => Setting::getSetting('session-prefix')->getValue()
|
'session-prefix' => Setting::getSetting('session-prefix')->getValue(),
|
||||||
|
'tags' => Tag::getAll()->toArray()
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,5 +16,11 @@ $ticketControllers->addController(new ReOpenController);
|
|||||||
$ticketControllers->addController(new ChangePriorityController);
|
$ticketControllers->addController(new ChangePriorityController);
|
||||||
$ticketControllers->addController(new SeenController);
|
$ticketControllers->addController(new SeenController);
|
||||||
$ticketControllers->addController(new DeleteController);
|
$ticketControllers->addController(new DeleteController);
|
||||||
|
$ticketControllers->addController(new CreateTagController);
|
||||||
|
$ticketControllers->addController(new EditTagController);
|
||||||
|
$ticketControllers->addController(new DeleteTagController);
|
||||||
|
$ticketControllers->addController(new GetTagsController);
|
||||||
|
$ticketControllers->addController(new AddTagController);
|
||||||
|
$ticketControllers->addController(new RemoveTagController);
|
||||||
|
|
||||||
$ticketControllers->finalize();
|
$ticketControllers->finalize();
|
||||||
|
61
server/controllers/ticket/add-tag.php
Normal file
61
server/controllers/ticket/add-tag.php
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
use Respect\Validation\Validator as DataValidator;
|
||||||
|
DataValidator::with('CustomValidations', true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} /ticket/add-tag Add tag
|
||||||
|
* @apiVersion 4.3.2
|
||||||
|
*
|
||||||
|
* @apiName Add tag
|
||||||
|
*
|
||||||
|
* @apiGroup Ticket
|
||||||
|
*
|
||||||
|
* @apiDescription This path attaches a new tag to a ticket.
|
||||||
|
*
|
||||||
|
* @apiPermission staff1
|
||||||
|
*
|
||||||
|
* @apiParam {Number} ticketNumber The number of the ticket which the tag is going to be attached.
|
||||||
|
* @apiParam {String} tagId The id of the tag to attach.
|
||||||
|
*
|
||||||
|
* @apiUse NO_PERMISSION
|
||||||
|
* @apiUse INVALID_TICKET
|
||||||
|
* @apiUse INVALID_TAG
|
||||||
|
* @apiUse TAG_EXISTS
|
||||||
|
*
|
||||||
|
* @apiSuccess {Object} data Empty object
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class AddTagController extends Controller {
|
||||||
|
const PATH = '/add-tag';
|
||||||
|
const METHOD = 'POST';
|
||||||
|
|
||||||
|
public function validations() {
|
||||||
|
return [
|
||||||
|
'permission' => 'staff_1',
|
||||||
|
'requestData' => [
|
||||||
|
'ticketNumber' => [
|
||||||
|
'validation' => DataValidator::validTicketNumber(),
|
||||||
|
'error' => ERRORS::INVALID_TICKET
|
||||||
|
],
|
||||||
|
'tagId' => [
|
||||||
|
'validation' => DataValidator::dataStoreId('tag'),
|
||||||
|
'error' => ERRORS::INVALID_TAG
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handler() {
|
||||||
|
$tagId = Controller::request('tagId');
|
||||||
|
$tag = Tag::getDataStore($tagId);
|
||||||
|
$ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber'));
|
||||||
|
|
||||||
|
if ($ticket->sharedTagList->includesId($tagId)) throw new RequestException(ERRORS::TAG_EXISTS);
|
||||||
|
|
||||||
|
$ticket->sharedTagList->add($tag);
|
||||||
|
$ticket->store();
|
||||||
|
|
||||||
|
Response::respondSuccess();
|
||||||
|
}
|
||||||
|
}
|
61
server/controllers/ticket/create-tag.php
Normal file
61
server/controllers/ticket/create-tag.php
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
use Respect\Validation\Validator as DataValidator;
|
||||||
|
DataValidator::with('CustomValidations', true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} /ticket/create-tag Create tag
|
||||||
|
* @apiVersion 4.3.2
|
||||||
|
*
|
||||||
|
* @apiName Create tag
|
||||||
|
*
|
||||||
|
* @apiGroup Ticket
|
||||||
|
*
|
||||||
|
* @apiDescription This path creates a new tag.
|
||||||
|
*
|
||||||
|
* @apiPermission staff1
|
||||||
|
*
|
||||||
|
* @apiParam {Number} name The new name of the tag.
|
||||||
|
* @apiParam {String} color The new color of the tag.
|
||||||
|
*
|
||||||
|
* @apiUse NO_PERMISSION
|
||||||
|
* @apiUse INVALID_NAME
|
||||||
|
* @apiUse TAG_EXISTS
|
||||||
|
*
|
||||||
|
* @apiSuccess {Object} data Empty object
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class CreateTagController extends Controller {
|
||||||
|
const PATH = '/create-tag';
|
||||||
|
const METHOD = 'POST';
|
||||||
|
|
||||||
|
public function validations() {
|
||||||
|
return [
|
||||||
|
'permission' => 'staff_1',
|
||||||
|
'requestData' => [
|
||||||
|
'name' => [
|
||||||
|
'validation' => DataValidator::length(2, 100),
|
||||||
|
'error' => ERRORS::INVALID_NAME
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handler() {
|
||||||
|
$name = Controller::request('name');
|
||||||
|
$color = Controller::request('color');
|
||||||
|
|
||||||
|
if (!Tag::getDataStore($name, 'name')->isNull()) {
|
||||||
|
throw new RequestException(ERRORS::TAG_EXISTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
$tagInstance = new Tag();
|
||||||
|
|
||||||
|
$tagInstance->setProperties([
|
||||||
|
'name' => $name,
|
||||||
|
'color' => $color
|
||||||
|
]);
|
||||||
|
$tagInstance->store();
|
||||||
|
Response::respondSuccess();
|
||||||
|
}
|
||||||
|
}
|
50
server/controllers/ticket/delete-tag.php
Normal file
50
server/controllers/ticket/delete-tag.php
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
use Respect\Validation\Validator as DataValidator;
|
||||||
|
DataValidator::with('CustomValidations', true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} /ticket/delete-tag Delete a tag
|
||||||
|
* @apiVersion 4.3.2
|
||||||
|
*
|
||||||
|
* @apiName Delete tag
|
||||||
|
*
|
||||||
|
* @apiGroup Ticket
|
||||||
|
*
|
||||||
|
* @apiDescription This path delete a tag.
|
||||||
|
*
|
||||||
|
* @apiPermission staff1
|
||||||
|
*
|
||||||
|
* @apiParam {Number} tagId The id of the tag.
|
||||||
|
*
|
||||||
|
* @apiUse NO_PERMISSION
|
||||||
|
* @apiUse INVALID_TAG
|
||||||
|
*
|
||||||
|
* @apiSuccess {Object} data Empty object
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class DeleteTagController extends Controller {
|
||||||
|
const PATH = '/delete-tag';
|
||||||
|
const METHOD = 'POST';
|
||||||
|
|
||||||
|
public function validations() {
|
||||||
|
return [
|
||||||
|
'permission' => 'staff_1',
|
||||||
|
'requestData' => [
|
||||||
|
'tagId' => [
|
||||||
|
'validation' => DataValidator::dataStoreId('tag'),
|
||||||
|
'error' => ERRORS::INVALID_TAG
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handler() {
|
||||||
|
|
||||||
|
$tagInstance = Tag::getDataStore(Controller::request('tagId'));
|
||||||
|
|
||||||
|
$tagInstance->delete();
|
||||||
|
|
||||||
|
Response::respondSuccess();
|
||||||
|
}
|
||||||
|
}
|
62
server/controllers/ticket/edit-tag.php
Normal file
62
server/controllers/ticket/edit-tag.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
use Respect\Validation\Validator as DataValidator;
|
||||||
|
DataValidator::with('CustomValidations', true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} /ticket/edit-tag Edit tag
|
||||||
|
* @apiVersion 4.3.2
|
||||||
|
*
|
||||||
|
* @apiName Edit tag
|
||||||
|
*
|
||||||
|
* @apiGroup Ticket
|
||||||
|
*
|
||||||
|
* @apiDescription This path edit tags.
|
||||||
|
*
|
||||||
|
* @apiPermission staff1
|
||||||
|
*
|
||||||
|
* @apiParam {Number} tagId The id of the tag.
|
||||||
|
* @apiParam {Number} name The new name of the tag.
|
||||||
|
* @apiParam {String} color The new color of the tag.
|
||||||
|
*
|
||||||
|
* @apiUse NO_PERMISSION
|
||||||
|
* @apiUse INVALID_TAG
|
||||||
|
* @apiUse TAG_EXISTS
|
||||||
|
*
|
||||||
|
* @apiSuccess {Object} data Empty object
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class EditTagController extends Controller {
|
||||||
|
const PATH = '/edit-tag';
|
||||||
|
const METHOD = 'POST';
|
||||||
|
|
||||||
|
public function validations() {
|
||||||
|
return [
|
||||||
|
'permission' => 'staff_1',
|
||||||
|
'requestData' => [
|
||||||
|
'tagId' => [
|
||||||
|
'validation' => DataValidator::dataStoreId('tag'),
|
||||||
|
'error' => ERRORS::INVALID_TAG
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handler() {
|
||||||
|
$name = Controller::request('name');
|
||||||
|
$color = Controller::request('color');
|
||||||
|
$tagInstance = Tag::getDataStore(Controller::request('tagId'));
|
||||||
|
|
||||||
|
if($name) $tagInstance->name = $name;
|
||||||
|
if($color) $tagInstance->color = $color;
|
||||||
|
|
||||||
|
$newNameTagInstance = Tag::getDataStore($name, 'name');
|
||||||
|
if (!$newNameTagInstance ->isNull() && $newNameTagInstance->id !== $tagInstance->id) {
|
||||||
|
throw new RequestException(ERRORS::TAG_EXISTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
$tagInstance->store();
|
||||||
|
|
||||||
|
Response::respondSuccess();
|
||||||
|
}
|
||||||
|
}
|
39
server/controllers/ticket/get-tags.php
Normal file
39
server/controllers/ticket/get-tags.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
use Respect\Validation\Validator as DataValidator;
|
||||||
|
DataValidator::with('CustomValidations', true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} /ticket/get-tags Get tags
|
||||||
|
* @apiVersion 4.3.2
|
||||||
|
*
|
||||||
|
* @apiName Get tags
|
||||||
|
*
|
||||||
|
* @apiGroup Ticket
|
||||||
|
*
|
||||||
|
* @apiDescription This path returns all the tags.
|
||||||
|
*
|
||||||
|
* @apiPermission staff1
|
||||||
|
*
|
||||||
|
* @apiUse NO_PERMISSION
|
||||||
|
*
|
||||||
|
* @apiSuccess {Object} data Empty object
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class GetTagsController extends Controller {
|
||||||
|
const PATH = '/get-tags';
|
||||||
|
const METHOD = 'POST';
|
||||||
|
|
||||||
|
public function validations() {
|
||||||
|
return [
|
||||||
|
'permission' => 'staff_1',
|
||||||
|
'requestData' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handler() {
|
||||||
|
$tags = Tag::getAll();
|
||||||
|
|
||||||
|
Response::respondSuccess($tags->toArray());
|
||||||
|
}
|
||||||
|
}
|
60
server/controllers/ticket/remove-tag.php
Normal file
60
server/controllers/ticket/remove-tag.php
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
use Respect\Validation\Validator as DataValidator;
|
||||||
|
DataValidator::with('CustomValidations', true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} /ticket/remove-tag Remove tag
|
||||||
|
* @apiVersion 4.3.2
|
||||||
|
*
|
||||||
|
* @apiName Remove tag
|
||||||
|
*
|
||||||
|
* @apiGroup Ticket
|
||||||
|
*
|
||||||
|
* @apiDescription This path removes a tag from a ticket.
|
||||||
|
*
|
||||||
|
* @apiPermission staff1
|
||||||
|
*
|
||||||
|
* @apiParam {Number} ticketNumber The number of the ticket which the tag is going to be removed.
|
||||||
|
* @apiParam {String} tagId The id of the tag to be removed.
|
||||||
|
*
|
||||||
|
* @apiUse NO_PERMISSION
|
||||||
|
* @apiUse INVALID_TICKET
|
||||||
|
* @apiUse INVALID_TAG
|
||||||
|
*
|
||||||
|
* @apiSuccess {Object} data Empty object
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class RemoveTagController extends Controller {
|
||||||
|
const PATH = '/remove-tag';
|
||||||
|
const METHOD = 'POST';
|
||||||
|
|
||||||
|
public function validations() {
|
||||||
|
return [
|
||||||
|
'permission' => 'staff_1',
|
||||||
|
'requestData' => [
|
||||||
|
'ticketNumber' => [
|
||||||
|
'validation' => DataValidator::validTicketNumber(),
|
||||||
|
'error' => ERRORS::INVALID_TICKET
|
||||||
|
],
|
||||||
|
'tagId' => [
|
||||||
|
'validation' => DataValidator::dataStoreId('tag'),
|
||||||
|
'error' => ERRORS::INVALID_TAG
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handler() {
|
||||||
|
$tagId = Controller::request('tagId');
|
||||||
|
$tag = Tag::getDataStore($tagId);
|
||||||
|
$ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber'));
|
||||||
|
|
||||||
|
if (!$ticket->sharedTagList->includesId($tagId)) throw new RequestException(ERRORS::INVALID_TAG);
|
||||||
|
|
||||||
|
$ticket->sharedTagList->remove($tag);
|
||||||
|
$ticket->store();
|
||||||
|
|
||||||
|
Response::respondSuccess();
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,11 @@
|
|||||||
* @apiDefine USER_EXISTS
|
* @apiDefine USER_EXISTS
|
||||||
* @apiError {String} USER_EXISTS The user already exists.
|
* @apiError {String} USER_EXISTS The user already exists.
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
|
* @apiDefine TAG_EXISTS
|
||||||
|
* @apiError {String} TAG_EXISTS The tag already exists.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
* @apiDefine NO_PERMISSION
|
* @apiDefine NO_PERMISSION
|
||||||
* @apiError {String} NO_PERMISSION You have no permission to perform this operation.
|
* @apiError {String} NO_PERMISSION You have no permission to perform this operation.
|
||||||
*/
|
*/
|
||||||
@ -47,7 +51,11 @@
|
|||||||
* @apiDefine INVALID_TICKET
|
* @apiDefine INVALID_TICKET
|
||||||
* @apiError {String} INVALID_TICKET The ticket is invalid.
|
* @apiError {String} INVALID_TICKET The ticket is invalid.
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
|
* @apiDefine INVALID_TAG
|
||||||
|
* @apiError {String} INVALID_TAG The tag is invalid.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
* @apiDefine INIT_SETTINGS_DONE
|
* @apiDefine INIT_SETTINGS_DONE
|
||||||
* @apiError {String} INIT_SETTINGS_DONE The init settings are already done.
|
* @apiError {String} INIT_SETTINGS_DONE The init settings are already done.
|
||||||
*/
|
*/
|
||||||
@ -240,6 +248,7 @@ class ERRORS {
|
|||||||
const INVALID_CREDENTIALS = 'INVALID_CREDENTIALS';
|
const INVALID_CREDENTIALS = 'INVALID_CREDENTIALS';
|
||||||
const SESSION_EXISTS = 'SESSION_EXISTS';
|
const SESSION_EXISTS = 'SESSION_EXISTS';
|
||||||
const USER_EXISTS = 'USER_EXISTS';
|
const USER_EXISTS = 'USER_EXISTS';
|
||||||
|
const TAG_EXISTS = 'TAG_EXISTS';
|
||||||
const NO_PERMISSION = 'NO_PERMISSION';
|
const NO_PERMISSION = 'NO_PERMISSION';
|
||||||
const INVALID_TITLE = 'INVALID_TITLE';
|
const INVALID_TITLE = 'INVALID_TITLE';
|
||||||
const INVALID_CONTENT = 'INVALID_CONTENT';
|
const INVALID_CONTENT = 'INVALID_CONTENT';
|
||||||
@ -249,6 +258,7 @@ class ERRORS {
|
|||||||
const INVALID_SETTING = 'INVALID_SETTING';
|
const INVALID_SETTING = 'INVALID_SETTING';
|
||||||
const INVALID_DEPARTMENT = 'INVALID_DEPARTMENT';
|
const INVALID_DEPARTMENT = 'INVALID_DEPARTMENT';
|
||||||
const INVALID_TICKET = 'INVALID_TICKET';
|
const INVALID_TICKET = 'INVALID_TICKET';
|
||||||
|
const INVALID_TAG = 'INVALID_TAG';
|
||||||
const INIT_SETTINGS_DONE = 'INIT_SETTINGS_DONE';
|
const INIT_SETTINGS_DONE = 'INIT_SETTINGS_DONE';
|
||||||
const INVALID_OLD_PASSWORD = 'INVALID_OLD_PASSWORD';
|
const INVALID_OLD_PASSWORD = 'INVALID_OLD_PASSWORD';
|
||||||
const INVALID_CAPTCHA = 'INVALID_CAPTCHA';
|
const INVALID_CAPTCHA = 'INVALID_CAPTCHA';
|
||||||
|
@ -19,7 +19,7 @@ abstract class Controller {
|
|||||||
$this->validate();
|
$this->validate();
|
||||||
$this->handler();
|
$this->handler();
|
||||||
} catch (\Exception $exception) {
|
} catch (\Exception $exception) {
|
||||||
Response::respondError($exception->getMessage(), $exception);
|
Response::respondError($exception->getMessage(), $exception, [$exception->__toString()]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -43,6 +43,9 @@ class DataStoreId extends AbstractRule {
|
|||||||
case 'customfield':
|
case 'customfield':
|
||||||
$dataStore = \Customfield::getDataStore($dataStoreId);
|
$dataStore = \Customfield::getDataStore($dataStoreId);
|
||||||
break;
|
break;
|
||||||
|
case 'tag':
|
||||||
|
$dataStore = \Tag::getDataStore($dataStoreId);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !$dataStore->isNull();
|
return !$dataStore->isNull();
|
||||||
@ -57,7 +60,8 @@ class DataStoreId extends AbstractRule {
|
|||||||
'customresponse',
|
'customresponse',
|
||||||
'topic',
|
'topic',
|
||||||
'article',
|
'article',
|
||||||
'customfield'
|
'customfield',
|
||||||
|
'tag'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
25
server/models/Tag.php
Normal file
25
server/models/Tag.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
//documentacion
|
||||||
|
class Tag extends DataStore {
|
||||||
|
const TABLE = 'tag';
|
||||||
|
|
||||||
|
public static function getProps() {
|
||||||
|
return [
|
||||||
|
'name',
|
||||||
|
'color'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray($minimized = false) {
|
||||||
|
if($minimized){
|
||||||
|
return $this->name;
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
'id'=> $this->id,
|
||||||
|
'name'=> $this->name,
|
||||||
|
'color' => $this->color
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -49,7 +49,8 @@ class Ticket extends DataStore {
|
|||||||
'unreadStaff',
|
'unreadStaff',
|
||||||
'language',
|
'language',
|
||||||
'authorEmail',
|
'authorEmail',
|
||||||
'authorName'
|
'authorName',
|
||||||
|
'sharedTagList'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +129,8 @@ class Ticket extends DataStore {
|
|||||||
'priority' => $this->priority,
|
'priority' => $this->priority,
|
||||||
'author' => $this->authorToArray(),
|
'author' => $this->authorToArray(),
|
||||||
'owner' => $this->ownerToArray(),
|
'owner' => $this->ownerToArray(),
|
||||||
'events' => $minimized ? [] : $this->eventsToArray()
|
'events' => $minimized ? [] : $this->eventsToArray(),
|
||||||
|
'tags' => $this->sharedTagList->toArray(true)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ require './system/add-department.rb'
|
|||||||
require './system/edit-department.rb'
|
require './system/edit-department.rb'
|
||||||
require './system/delete-department.rb'
|
require './system/delete-department.rb'
|
||||||
require './staff/last-events.rb'
|
require './staff/last-events.rb'
|
||||||
require './system/mail-templates.rb'
|
# require './system/mail-templates.rb'
|
||||||
require './system/disable-registration.rb'
|
require './system/disable-registration.rb'
|
||||||
require './system/enable-registration.rb'
|
require './system/enable-registration.rb'
|
||||||
require './system/add-api-key.rb'
|
require './system/add-api-key.rb'
|
||||||
@ -62,6 +62,11 @@ require './system/delete-api-key.rb'
|
|||||||
require './system/get-api-keys.rb'
|
require './system/get-api-keys.rb'
|
||||||
require './system/file-upload-download.rb'
|
require './system/file-upload-download.rb'
|
||||||
require './system/csv-import.rb'
|
require './system/csv-import.rb'
|
||||||
require './system/custom-fields.rb'
|
require './ticket/create-tag.rb'
|
||||||
|
require './ticket/edit-tag.rb'
|
||||||
|
require './ticket/get-tags.rb'
|
||||||
|
require './ticket/delete-tag.rb'
|
||||||
|
require './ticket/add-tag.rb'
|
||||||
|
require './ticket/delete-tag.rb'
|
||||||
require './system/disable-user-system.rb'
|
require './system/disable-user-system.rb'
|
||||||
require './system/get-stats.rb'
|
require './system/get-stats.rb'
|
||||||
|
@ -20,7 +20,7 @@ class Scripts
|
|||||||
departments = request('/system/get-settings', {
|
departments = request('/system/get-settings', {
|
||||||
csrf_userid: $csrf_userid,
|
csrf_userid: $csrf_userid,
|
||||||
csrf_token: $csrf_token
|
csrf_token: $csrf_token
|
||||||
})['departments']
|
})['data']['departments']
|
||||||
departments = departments.collect { |x| x.id }
|
departments = departments.collect { |x| x.id }
|
||||||
|
|
||||||
response = request('/staff/add', {
|
response = request('/staff/add', {
|
||||||
@ -107,4 +107,12 @@ class Scripts
|
|||||||
description: description
|
description: description
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
def self.createTag(name, color)
|
||||||
|
request('/ticket/create-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
name: name,
|
||||||
|
color: color
|
||||||
|
})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
9
tests/system/custom-field-test.rb
Normal file
9
tests/system/custom-field-test.rb
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
describe 'CustomField' do
|
||||||
|
request('/user/logout')
|
||||||
|
Scripts.login($staff[:email], $staff[:password], true)
|
||||||
|
|
||||||
|
describe '/system/add-custom field' do
|
||||||
|
it 'should add custom field with departments' do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
56
tests/ticket/add-tag.rb
Normal file
56
tests/ticket/add-tag.rb
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
describe '/ticket/add-tag' do
|
||||||
|
request('/user/logout')
|
||||||
|
Scripts.login($staff[:email], $staff[:password], true)
|
||||||
|
|
||||||
|
Scripts.createTag('test tag', 'orange')
|
||||||
|
result = Scripts.createTicket('test ticket')
|
||||||
|
|
||||||
|
@ticketNumber = result['ticketNumber']
|
||||||
|
|
||||||
|
it 'should fail if the tagId is invalid' do
|
||||||
|
result = request('/ticket/add-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
tagId: 100,
|
||||||
|
ticketNumber: @ticketNumber
|
||||||
|
})
|
||||||
|
(result['status']).should.equal('fail')
|
||||||
|
(result['message']).should.equal('INVALID_TAG')
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should fail if the ticketNumber is invalid' do
|
||||||
|
result = request('/ticket/add-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
tagId: 3,
|
||||||
|
ticketNumber: 0
|
||||||
|
})
|
||||||
|
(result['status']).should.equal('fail')
|
||||||
|
(result['message']).should.equal('INVALID_TICKET')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should add a tag' do
|
||||||
|
result = request('/ticket/add-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
tagId: 3,
|
||||||
|
ticketNumber: @ticketNumber
|
||||||
|
})
|
||||||
|
tag_ticket = $database.getRow('tag_ticket', 3 , 'id')
|
||||||
|
ticket = $database.getRow('ticket', @ticketNumber ,'ticket_number')
|
||||||
|
|
||||||
|
(result['status']).should.equal('success')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should fail if the tag is already attached' do
|
||||||
|
result = request('/ticket/add-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
tagId: 3,
|
||||||
|
ticketNumber: @ticketNumber
|
||||||
|
})
|
||||||
|
(result['status']).should.equal('fail')
|
||||||
|
(result['message']).should.equal('TAG_EXISTS')
|
||||||
|
end
|
||||||
|
end
|
64
tests/ticket/create-tag.rb
Normal file
64
tests/ticket/create-tag.rb
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
describe '/ticket/create-tag' do
|
||||||
|
request('/user/logout')
|
||||||
|
Scripts.login($staff[:email], $staff[:password], true)
|
||||||
|
|
||||||
|
it 'should add a tag' do
|
||||||
|
result = request('/ticket/create-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
name: 'tag1',
|
||||||
|
color: 'blue'
|
||||||
|
})
|
||||||
|
tag = $database.getRow('tag', 1 , 'id')
|
||||||
|
|
||||||
|
(result['status']).should.equal('success')
|
||||||
|
(tag['name']).should.equal('tag1')
|
||||||
|
(tag['color']).should.equal('blue')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should not add tag if already exits' do
|
||||||
|
result = request('/ticket/create-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
name: 'tag1',
|
||||||
|
color: 'blue'
|
||||||
|
})
|
||||||
|
|
||||||
|
(result['status']).should.equal('fail')
|
||||||
|
(result['message']).should.equal('TAG_EXISTS')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should fail if the name is invalid' do
|
||||||
|
result = request('/ticket/create-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
color: 'black'
|
||||||
|
})
|
||||||
|
|
||||||
|
(result['status']).should.equal('fail')
|
||||||
|
(result['message']).should.equal('INVALID_NAME')
|
||||||
|
|
||||||
|
result = request('/ticket/create-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
name: 'T',
|
||||||
|
color: 'black'
|
||||||
|
})
|
||||||
|
|
||||||
|
(result['status']).should.equal('fail')
|
||||||
|
(result['message']).should.equal('INVALID_NAME')
|
||||||
|
|
||||||
|
long_text = ''
|
||||||
|
200.times {long_text << 'a'}
|
||||||
|
|
||||||
|
result = request('/ticket/create-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
name: long_text,
|
||||||
|
color: 'black'
|
||||||
|
})
|
||||||
|
|
||||||
|
(result['status']).should.equal('fail')
|
||||||
|
(result['message']).should.equal('INVALID_NAME')
|
||||||
|
end
|
||||||
|
end
|
38
tests/ticket/delete-tag.rb
Normal file
38
tests/ticket/delete-tag.rb
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
describe '/ticket/delete-tag' do
|
||||||
|
|
||||||
|
it 'should fail if a user is logged' do
|
||||||
|
Scripts.login
|
||||||
|
|
||||||
|
result = request('/ticket/delete-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
tagId: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
(result['status']).should.equal('fail')
|
||||||
|
(result['message']).should.equal('NO_PERMISSION')
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
it 'should delete a tag if is a Staff 3 logged' do
|
||||||
|
Scripts.login($staff[:email], $staff[:password], true)
|
||||||
|
result = request('/ticket/delete-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
tagId: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
(result['status']).should.equal('success')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should fail if the tagId is invalid' do
|
||||||
|
result = request('/ticket/delete-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
tagId: 1000
|
||||||
|
})
|
||||||
|
|
||||||
|
(result['status']).should.equal('fail')
|
||||||
|
(result['message']).should.equal('INVALID_TAG')
|
||||||
|
end
|
||||||
|
end
|
50
tests/ticket/edit-tag.rb
Normal file
50
tests/ticket/edit-tag.rb
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
describe '/ticket/edit-tag' do
|
||||||
|
request('/user/logout')
|
||||||
|
Scripts.login($staff[:email], $staff[:password], true)
|
||||||
|
|
||||||
|
it 'should edit a tag' do
|
||||||
|
result = request('/ticket/edit-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
tagId: 1,
|
||||||
|
name: 'TAG1',
|
||||||
|
color: 'yellow'
|
||||||
|
})
|
||||||
|
(result['status']).should.equal('success')
|
||||||
|
|
||||||
|
tag = $database.getRow('tag', 1, 'id')
|
||||||
|
|
||||||
|
(tag['name']).should.equal('TAG1')
|
||||||
|
(tag['color']).should.equal('yellow')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should fail if the name already exists' do
|
||||||
|
request('/ticket/create-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
name: 'TAG2',
|
||||||
|
color: 'blue'
|
||||||
|
})
|
||||||
|
|
||||||
|
result = request('/ticket/edit-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
tagId: 2,
|
||||||
|
name: 'TAG1'
|
||||||
|
})
|
||||||
|
|
||||||
|
(result['status']).should.equal('fail')
|
||||||
|
(result['message']).should.equal('TAG_EXISTS')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should fail if the tagId is invalid' do
|
||||||
|
result = request('/ticket/edit-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
tagId: 100
|
||||||
|
})
|
||||||
|
|
||||||
|
(result['status']).should.equal('fail')
|
||||||
|
(result['message']).should.equal('INVALID_TAG')
|
||||||
|
end
|
||||||
|
end
|
38
tests/ticket/get-tags.rb
Normal file
38
tests/ticket/get-tags.rb
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
describe '/ticket/get-tags' do
|
||||||
|
|
||||||
|
it 'should fail if a user is logged' do
|
||||||
|
Scripts.login('steve@jobs.com', 'custompassword')
|
||||||
|
|
||||||
|
result = request('/ticket/get-tags', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token
|
||||||
|
})
|
||||||
|
|
||||||
|
(result['status']).should.equal('fail')
|
||||||
|
(result['message']).should.equal('NO_PERMISSION')
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
it 'should get the tags if is a Staff 3 logged' do
|
||||||
|
Scripts.login($staff[:email], $staff[:password], true)
|
||||||
|
|
||||||
|
request('/ticket/create-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
name: 'TAG3',
|
||||||
|
color: 'grey'
|
||||||
|
})
|
||||||
|
result = request('/ticket/get-tags', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token
|
||||||
|
})
|
||||||
|
|
||||||
|
(result['status']).should.equal('success')
|
||||||
|
(result['data'][0]['name']).should.equal('TAG1')
|
||||||
|
(result['data'][0]['color']).should.equal('yellow')
|
||||||
|
(result['data'][1]['name']).should.equal('TAG2')
|
||||||
|
(result['data'][1]['color']).should.equal('blue')
|
||||||
|
(result['data'][2]['name']).should.equal('TAG3')
|
||||||
|
(result['data'][2]['color']).should.equal('grey')
|
||||||
|
end
|
||||||
|
end
|
56
tests/ticket/remove-tag.rb
Normal file
56
tests/ticket/remove-tag.rb
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
describe '/ticket/remove-tag' do
|
||||||
|
request('/user/logout')
|
||||||
|
Scripts.login($staff[:email], $staff[:password], true)
|
||||||
|
|
||||||
|
result = $database.getRow('ticket', 'test ticket' , 'title')
|
||||||
|
tag = $database.getRow('tag', 'test tag', 'name')
|
||||||
|
|
||||||
|
@ticketNumber = result['ticketNumber']
|
||||||
|
|
||||||
|
it 'should fail if the ticket number is invalid'do
|
||||||
|
result = request('/ticket/remove-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
tagId: tag['id'],
|
||||||
|
ticketNumber: 0
|
||||||
|
})
|
||||||
|
(result['status']).should.equal('fail')
|
||||||
|
(result['message']).should.equal('INVALID_TICKET')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should fail is the tag id is not valid'do
|
||||||
|
result = request('/ticket/remove-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
tagId: 100,
|
||||||
|
ticketNumber: @ticketNumber
|
||||||
|
})
|
||||||
|
(result['status']).should.equal('fail')
|
||||||
|
(result['message']).should.equal('INVALID_TAG')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should remove an attached tag' do
|
||||||
|
result = request('/ticket/remove-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
tagId: tag['id'],
|
||||||
|
ticketNumber: @ticketNumber
|
||||||
|
})
|
||||||
|
|
||||||
|
(result['status']).should.equal('success')
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
it 'should fail if the tag is not attached' do
|
||||||
|
result = request('/ticket/remove-tag', {
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token,
|
||||||
|
tagId: tag['id'],
|
||||||
|
ticketNumber: @ticketNumber
|
||||||
|
})
|
||||||
|
|
||||||
|
(result['status']).should.equal('fail')
|
||||||
|
(result['message']).should.equal('INVALID_TAG')
|
||||||
|
end
|
||||||
|
end
|
@ -3,7 +3,7 @@ describe '/user/edit-email' do
|
|||||||
request('/user/logout')
|
request('/user/logout')
|
||||||
result = request('/user/login', {
|
result = request('/user/login', {
|
||||||
email: 'steve@jobs.com',
|
email: 'steve@jobs.com',
|
||||||
password: 'newpassword'
|
password: 'custompassword'
|
||||||
})
|
})
|
||||||
|
|
||||||
$csrf_userid = result['data']['userId']
|
$csrf_userid = result['data']['userId']
|
||||||
@ -35,5 +35,11 @@ describe '/user/edit-email' do
|
|||||||
csrf_token: $csrf_token
|
csrf_token: $csrf_token
|
||||||
})
|
})
|
||||||
(result['status']).should.equal('success')
|
(result['status']).should.equal('success')
|
||||||
|
|
||||||
|
result = request('/user/edit-email', {
|
||||||
|
newEmail: 'steve@jobs.com',
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token
|
||||||
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -3,7 +3,7 @@ describe '/user/edit-password' do
|
|||||||
request('/user/logout')
|
request('/user/logout')
|
||||||
result = request('/user/login', {
|
result = request('/user/login', {
|
||||||
email: 'steve@jobs.com',
|
email: 'steve@jobs.com',
|
||||||
password: 'custom'
|
password: 'custompassword'
|
||||||
})
|
})
|
||||||
|
|
||||||
$csrf_userid = result['data']['userId']
|
$csrf_userid = result['data']['userId']
|
||||||
@ -12,7 +12,7 @@ describe '/user/edit-password' do
|
|||||||
|
|
||||||
it 'should fail if new password is incorrect' do
|
it 'should fail if new password is incorrect' do
|
||||||
result = request('/user/edit-password', {
|
result = request('/user/edit-password', {
|
||||||
oldPassword: 'custom',
|
oldPassword: 'custompassword',
|
||||||
newPassword: 'np',
|
newPassword: 'np',
|
||||||
csrf_userid: $csrf_userid,
|
csrf_userid: $csrf_userid,
|
||||||
csrf_token: $csrf_token
|
csrf_token: $csrf_token
|
||||||
@ -24,7 +24,7 @@ describe '/user/edit-password' do
|
|||||||
250.times {long_text << 'a'}
|
250.times {long_text << 'a'}
|
||||||
|
|
||||||
result = request('/user/edit-password', {
|
result = request('/user/edit-password', {
|
||||||
oldPassword: 'custom',
|
oldPassword: 'custompassword',
|
||||||
newPassword: long_text,
|
newPassword: long_text,
|
||||||
csrf_userid: $csrf_userid,
|
csrf_userid: $csrf_userid,
|
||||||
csrf_token: $csrf_token
|
csrf_token: $csrf_token
|
||||||
@ -46,7 +46,7 @@ describe '/user/edit-password' do
|
|||||||
|
|
||||||
it 'should change password' do
|
it 'should change password' do
|
||||||
result = request('/user/edit-password',{
|
result = request('/user/edit-password',{
|
||||||
oldPassword: 'custom',
|
oldPassword: 'custompassword',
|
||||||
newPassword: 'newpassword',
|
newPassword: 'newpassword',
|
||||||
csrf_userid: $csrf_userid,
|
csrf_userid: $csrf_userid,
|
||||||
csrf_token: $csrf_token
|
csrf_token: $csrf_token
|
||||||
@ -55,9 +55,13 @@ describe '/user/edit-password' do
|
|||||||
|
|
||||||
request('/user/logout')
|
request('/user/logout')
|
||||||
|
|
||||||
result = request('/user/login',{
|
Scripts.login('steve@jobs.com','newpassword')
|
||||||
email: 'steve@jobs.com',
|
|
||||||
password: 'newpassword'
|
result = request('/user/edit-password',{
|
||||||
|
oldPassword: 'newpassword',
|
||||||
|
newPassword: 'custompassword',
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token
|
||||||
})
|
})
|
||||||
(result['status']).should.equal('success')
|
(result['status']).should.equal('success')
|
||||||
end
|
end
|
||||||
|
@ -3,7 +3,7 @@ describe '/user/signup' do
|
|||||||
response = request('/user/signup', {
|
response = request('/user/signup', {
|
||||||
:name => 'Steve Jobs',
|
:name => 'Steve Jobs',
|
||||||
:email => 'steve@jobs.com',
|
:email => 'steve@jobs.com',
|
||||||
:password => 'custom'
|
:password => 'custompassword'
|
||||||
})
|
})
|
||||||
|
|
||||||
userRow = $database.getRow('user', response['data']['userId'])
|
userRow = $database.getRow('user', response['data']['userId'])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user