Merge pull request #678 from guillegiu/master

Add ticket Search Path
This commit is contained in:
Guillermo Giuliana 2019-12-29 15:58:32 -03:00 committed by GitHub
commit 9f4a293107
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1093 additions and 22 deletions

View File

@ -274,4 +274,4 @@ export default connect((store) => {
return {
tags: store.config['tags']
};
})(TicketList);
})(TicketList);

View File

@ -0,0 +1,90 @@
import React from 'react';
import _ from 'lodash';
import API from 'lib-app/api-call';
import i18n from 'lib-app/i18n';
import {connect} from 'react-redux';
import TicketList from 'app-components/ticket-list';
import Message from 'core-components/message';
class TicketQueryList extends React.Component {
state = {
tickets: [],
page: 1,
pages: 0,
error: null,
loading: true
};
componentDidMount() {
this.getTickets();
}
componentDidUpdate(prevProps) {
if (this.props.customList.title !== prevProps.customList.title) {
this.getTickets();
}
}
render() {
return (
<div>
{(this.state.error) ? <Message type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> : <TicketList {...this.getTicketListProps()}/>}
</div>
);
}
getTickets() {
this.setState({
loading:true
})
API.call({
path: '/ticket/search',
data: {
page : this.state.page,
...this.props.customList.filters
}
}).then((result) => {
this.setState({
tickets: result.data.tickets,
page: result.data.page,
pages: result.data.pages,
error: null,
loading: false
})
}).catch((result) => this.setState({
loading: false,
error: result.message
}));
}
onPageChange(event) {
this.setState({page: event.target.value}, () => this.getTickets());
}
getTicketListProps () {
const {page,pages,loading,tickets} = this.state;
return {
userId: this.props.userId,
ticketPath: '/admin/panel/tickets/view-ticket/',
tickets,
page,
pages,
loading,
type: 'secondary',
showDepartmentDropdown: false,
closedTicketsShown: false,
onPageChange:this.onPageChange.bind(this)
};
}
}
export default connect((store) => {
return {
userId: store.session.userId
};
})(TicketQueryList);

View File

@ -33,6 +33,7 @@ import AdminPanelMyAccount from 'app/admin/panel/dashboard/admin-panel-my-accoun
import AdminPanelMyTickets from 'app/admin/panel/tickets/admin-panel-my-tickets';
import AdminPanelNewTickets from 'app/admin/panel/tickets/admin-panel-new-tickets';
import AdminPanelAllTickets from 'app/admin/panel/tickets/admin-panel-all-tickets';
import AdminPanelSearchTickets from 'app/admin/panel/tickets/admin-panel-search-tickets';
import AdminPanelViewTicket from 'app/admin/panel/tickets/admin-panel-view-ticket';
import AdminPanelCustomResponses from 'app/admin/panel/tickets/admin-panel-custom-responses';
@ -113,6 +114,7 @@ export default (
<Route path="my-tickets" component={AdminPanelMyTickets} />
<Route path="new-tickets" component={AdminPanelNewTickets} />
<Route path="all-tickets" component={AdminPanelAllTickets} />
<Route path="search-tickets" component={AdminPanelSearchTickets} />
<Route path="custom-responses" component={AdminPanelCustomResponses} />
<Route path="view-ticket/:ticketNumber" component={AdminPanelViewTicket} />
</Route>

View File

@ -76,7 +76,7 @@ class AdminPanelMenu extends React.Component {
getGroupItemIndex() {
const group = this.getRoutes()[this.getGroupIndex()];
const pathname = this.props.location.pathname;
const pathname = this.props.location.pathname + this.props.location.search;
return _.findIndex(group.items, {path: pathname});
}
@ -90,7 +90,23 @@ class AdminPanelMenu extends React.Component {
return (groupIndex === -1) ? 0 : groupIndex;
}
getCustomlists() {
if(window.customTicketList){
return window.customTicketList.map((item, index) => {
return {
name: item.title,
path: '/admin/panel/tickets/search-tickets?custom=' + index,
level: 1
}
})
} else {
return [];
}
}
getRoutes() {
const customLists = this.getCustomlists();
return this.getItemsByFilteredByLevel(_.without([
{
groupName: i18n('DASHBOARD'),
@ -135,7 +151,8 @@ class AdminPanelMenu extends React.Component {
name: i18n('CUSTOM_RESPONSES'),
path: '/admin/panel/tickets/custom-responses',
level: 2
}
},
...customLists
])
},
this.props.config['user-system-enabled'] ? {

View File

@ -0,0 +1,34 @@
import React from 'react';
import {connect} from 'react-redux';
import i18n from 'lib-app/i18n';
import TicketQueryList from 'app-components/ticket-query-list';
import Header from 'core-components/header';
import Message from 'core-components/message';
class AdminPanelSearchTickets extends React.Component {
render() {
return (
<div className="admin-panel-all-tickets">
<Header title={i18n('ALL_TICKETS')} description={i18n('SEARCH_TICKETS_DESCRIPTION')} />
{(this.props.error) ? <Message type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> : <TicketQueryList customList ={this.getFilters()}/>}
</div>
);
}
getFilters() {
let customList = window.customTicketList[this.props.location.query.custom*1] ? window.customTicketList[this.props.location.query.custom*1] : null
return {
...customList
};
}
}
export default connect((store) => {
return {
error: store.adminData.allTicketsError
};
})(AdminPanelSearchTickets);

View File

@ -140,4 +140,4 @@ class Menu extends React.Component {
}
}
export default Menu;
export default Menu;

View File

@ -201,4 +201,4 @@ $transition: background-color 0.3s ease, color 0.3s ease;
color: white;
}
}
}
}

View File

@ -98,4 +98,4 @@ class Pagination extends React.Component {
}
}
export default Pagination;
export default Pagination;

View File

@ -55,7 +55,7 @@ class Table extends React.Component {
'table__header-column': true,
[header.className]: (header.className)
};
return (
<th className={classNames(classes)} key={header.key}>
{header.value}
@ -97,7 +97,7 @@ class Table extends React.Component {
};
return (
<td className={classNames(classes)} key={key}>{row[key]}</td>
);
}
@ -139,15 +139,15 @@ class Table extends React.Component {
this.props.onPageChange({target: {value: index}});
}
}
getRowClass(row) {
let classes = {
'table__row': true,
'table__row-highlighted': row.highlighted
};
classes[row.className] = (row.className);
return classNames(classes);
}
@ -167,4 +167,4 @@ class Table extends React.Component {
}
}
export default Table;
export default Table;

View File

@ -94,6 +94,7 @@ export default {
'CHANGE_EMAIL': 'Change email',
'CHANGE_PASSWORD': 'Change password',
'NAME': 'Name',
'SEARCH': 'Search',
'SIGNUP_DATE': 'Sign up date',
'SEARCH_USERS': 'Search users...',
'SEARCH_EMAIL': 'Search email...',
@ -297,6 +298,7 @@ export default {
'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.',
'ALL_TICKETS_DESCRIPTION': 'Here you can view the tickets of the departments you are assigned.',
'SEARCH_TICKETS_DESCRIPTION': 'Here you can search tickets by specific filters',
'TICKET_VIEW_DESCRIPTION': 'This ticket has been sent by a customer. Here you can respond or assign the ticket',
'BAN_USERS_DESCRIPTION': 'Here you can see a list of banned emails, you can un-ban them or add more emails to the list.',
'LIST_USERS_DESCRIPTION': 'This is the list of users that are registered in this platform. You can search for someone in particular, delete it or ban it.',

View File

@ -12,7 +12,6 @@
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
<body>
<div id="app"></div>
<script src="/config.js"></script>
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=String.prototype.startsWith,Array.from,Array.prototype.fill,Array.prototype.keys,Array.prototype.find,Array.prototype.findIndex,Array.prototype.includes,String.prototype.repeat,Number.isInteger,Promise&flags=gated"></script>
</body>

View File

@ -50,7 +50,7 @@ class GetAllTicketsStaffController extends Controller {
]);
return;
}
Response::respondSuccess([
'tickets' => $this->getTicketList()->toArray(true),
'pages' => $this->getTotalPages()

View File

@ -23,5 +23,6 @@ $ticketControllers->addController(new DeleteTagController);
$ticketControllers->addController(new GetTagsController);
$ticketControllers->addController(new AddTagController);
$ticketControllers->addController(new RemoveTagController);
$ticketControllers->addController(new SearchController);
$ticketControllers->finalize();

View File

@ -0,0 +1,348 @@
<?php
use Respect\Validation\Validator as DataValidator;
use RedBeanPHP\Facade as RedBean;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/search Search tickets
* @apiVersion 4.5.0
*
* @apiName Search ticket
*
* @apiGroup Ticket
*
* @apiDescription This path search specific tickets.
*
* @apiPermission user
*
* @apiParam {Number[]} tags The ids of the tags to make a custom search.
* @apiParam {Number} closed The status of closed 1 or 0 to make a custom search.
* @apiParam {Number} unreadStaff The status of unread_staff 1 or 0 to make a custom search.
* @apiParam {Number[]} priority The values of priority to make a custom search.
* @apiParam {Number[]} dateRange The numbers of the range of date to make a custom search.
* @apiParam {Number[]} departments The ids of the departments to make a custom search.
* @apiParam {Object[]} authors A object {id,staff} with id and boolean to make a custom search.
* @apiParam {Number} assigned The status of assigned 1 or 0 to make a custom search.
* @apiParam {String} query A string to find into a ticket to make a custom search.
* @apiParam {Number} page The number of the page of the tickets.
* @apiParam {Object} orderBy A object {value, asc}with string and boolean to make a especific order of the search.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TAG_FILTER
* @apiUse INVALID_CLOSED_FILTER
* @apiUse INVALID_UNREAD_STAFF_FILTER
* @apiUse INVALID_PRIORITY_FILTER
* @apiUse INVALID_DATE_RANGE_FILTER
* @apiUse INVALID_DEPARTMENT_FILTER
* @apiUse INVALID_AUTHOR_FILTER
* @apiUse INVALID_ASSIGNED_FILTER
* @apiUse INVALID_ORDER_BY
* @apiUse INVALID_PAGE
*
* @apiSuccess {Object} data Empty object
*
*
*/
class SearchController extends Controller {
const PATH = '/search';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => [
'page' => [
'validation' => DataValidator::oneOf(DataValidator::numeric()->positive(),DataValidator::nullType()),
'error' => ERRORS::INVALID_PAGE
],
'tags' => [
'validation' => DataValidator::oneOf(DataValidator::validTagsId(),DataValidator::nullType()),
'error' => ERRORS::INVALID_TAG_FILTER
],
'closed' => [
'validation' => DataValidator::oneOf(DataValidator::in(['0','1']),DataValidator::nullType()),
'error' => ERRORS::INVALID_CLOSED_FILTER
],
'unreadStaff' => [
'validation' => DataValidator::oneOf(DataValidator::in(['0','1']),DataValidator::nullType()),
'error' => ERRORS::INVALID_UNREAD_STAFF_FILTER
],
'priority' => [
'validation' => DataValidator::oneOf(DataValidator::validPriorities(),DataValidator::nullType()),
'error' => ERRORS::INVALID_PRIORITY_FILTER
],
'dateRange' => [
'validation' => DataValidator::oneOf(DataValidator::validDateRange(),DataValidator::nullType()),
'error' => ERRORS::INVALID_DATE_RANGE_FILTER
],
'departments' => [
'validation' => DataValidator::oneOf(DataValidator::validDepartmentsId(),DataValidator::nullType()),
'error' => ERRORS::INVALID_DEPARTMENT_FILTER
],
'authors' => [
'validation' => DataValidator::oneOf(DataValidator::validAuthorsId(),DataValidator::nullType()),
'error' => ERRORS::INVALID_AUTHOR_FILTER
],
'assigned' => [
'validation' => DataValidator::oneOf(DataValidator::in(['0','1']),DataValidator::nullType()),
'error' => ERRORS::INVALID_ASSIGNED_FILTER
],
'orderBy' => [
'validation' => DataValidator::oneOf(DataValidator::validOrderBy(),DataValidator::nullType()),
'error' => ERRORS::INVALID_ORDER_BY
],
]
];
}
public function handler() {
$inputs = [
'closed' => Controller::request('closed'),
'tags' => json_decode(Controller::request('tags')),
'unreadStaff' => Controller::request('unreadStaff'),
'priority' => json_decode(Controller::request('priority')),
'dateRange' => json_decode(Controller::request('dateRange')),
'departments' => json_decode(Controller::request('departments')),
'authors' => json_decode(Controller::request('authors'),true),
'assigned' => Controller::request('assigned'),
'query' => Controller::request('query'),
'orderBy' => json_decode(Controller::request('orderBy'),true),
'page' => Controller::request('page'),
'allowedDepartments' => Controller::getLoggedUser()->sharedDepartmentList->toArray(),
];
$query = $this->getSQLQuery($inputs);
$queryWithOrder = $this->getSQLQueryWithOrder($inputs);
$totalCount = RedBean::getAll("SELECT COUNT(*) FROM (SELECT COUNT(*) " . $query . " ) AS T2", [':query' => $inputs['query']])[0]['COUNT(*)'];
$ticketIdList = RedBean::getAll($queryWithOrder, [':query' => "%" . $inputs['query'] . "%"]);
$ticketList = [];
foreach ($ticketIdList as $item) {
$ticket = Ticket::getDataStore($item['id']);
array_push($ticketList, $ticket->toArray());
}
$ticketTableExists = RedBean::exec("select table_name from information_schema.tables where table_name = 'ticket';");
if($ticketTableExists){
Response::respondSuccess([
'tickets' => $ticketList,
'pages' => ceil($totalCount / 10),
'page' => $inputs['page'] ? ($inputs['page']*1) : 1
]);
}else{
Response::respondSuccess([]);
}
}
public function getSQLQuery($inputs) {
$tagsTableExists = RedBean::exec("select table_name from information_schema.tables where table_name = 'tag_ticket';");
$ticketEventTableExists = RedBean::exec("select table_name from information_schema.tables where table_name = 'ticketevent';");
$taglistQuery = ( $tagsTableExists ? " LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id" : '');
$ticketeventlistQuery = ( $ticketEventTableExists ? " LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id" : '');
$query = "FROM (ticket" . $taglistQuery . $ticketeventlistQuery .")";
$filters = "";
$this->setQueryFilters($inputs, $filters);
$query .= $filters . " GROUP BY ticket.id";
return $query;
}
public function getSQLQueryWithOrder($inputs) {
$query = $this->getSQLQuery($inputs);
$order = "";
$query = "SELECT" . " ticket.id " . $query;
$this->setQueryOrder($inputs, $order);
$inputs['page'] ? $page = $inputs['page'] : $page = 1 ;
$query .= $order ." LIMIT 10 OFFSET " . (($page-1)*10);
return $query;
}
//FILTER
private function setQueryFilters($inputs, &$filters){
if(array_key_exists('tags',$inputs)) $this->setTagFilter($inputs['tags'], $filters);
if(array_key_exists('closed',$inputs)) $this->setClosedFilter($inputs['closed'], $filters);
if(array_key_exists('assigned',$inputs)) $this->setAssignedFilter($inputs['assigned'], $filters);
if(array_key_exists('unreadStaff',$inputs)) $this->setSeenFilter($inputs['unreadStaff'], $filters);
if(array_key_exists('priority',$inputs)) $this->setPriorityFilter($inputs['priority'], $filters);
if(array_key_exists('dateRange',$inputs)) $this->setDateFilter($inputs['dateRange'], $filters);
if(array_key_exists('departments',$inputs)) $this->setDepartmentFilter($inputs['departments'],$inputs['allowedDepartments'], $filters);
if(array_key_exists('authors',$inputs)) $this->setAuthorFilter($inputs['authors'], $filters);
if(array_key_exists('query',$inputs)) $this->setStringFilter($inputs['query'], $filters);
if($filters != "") $filters = " WHERE " . $filters;
}
private function setTagFilter($tagList, &$filters){
$tagsTableExists = RedBean::exec("select table_name from information_schema.tables where table_name = 'tag_ticket';");
if($tagList && $tagsTableExists){
$filters != "" ? $filters .= " and " : null;
foreach($tagList as $key => $tag) {
$key == 0 ? $filters .= " ( " : null;
($key != 0 && $key != sizeof($tagList)) ? $filters .= " or " : null;
$filters .= "tag_ticket.tag_id = " . $tag ;
}
$filters .= ")";
}
}
public function setClosedFilter($closed, &$filters){
if ($closed !== null) {
if ($filters != "") $filters .= " and ";
$filters .= "ticket.closed = " . $closed ;
}
}
private function setSeenFilter($unreadStaff, &$filters){
if ($unreadStaff !== null) {
if ($filters != "") $filters .= " and ";
$filters .= "ticket.unread_staff = " . $unreadStaff;
}
}
private function setPriorityFilter($priorities, &$filters){
if($priorities !== null){
$first = TRUE;
if ($filters != "") $filters .= " and ";
foreach(array_unique($priorities) as $priority) {
if($first){
$filters .= " ( ";
$first = FALSE;
} else {
$filters .= " or ";
}
if($priority == 0){
$filters .= "ticket.priority = 'low'";
}elseif($priority == 1){
$filters .= "ticket.priority = 'medium'";
}elseif($priority == 2){
$filters .= "ticket.priority = 'high'";
}
}
$priorities != "" ? $filters .= ") " : null;
}
}
private function setDateFilter($dateRange, &$filters){
if ($dateRange !== null) {
if ($filters != "") $filters .= " and ";
foreach($dateRange as $key => $date) {
$key == 0 ? ($filters .= "(ticket.date >= " . $date ): ($filters .= " and ticket.date <= " . $date . ")");
}
}
}
private function setDepartmentFilter($departments,$allowedDepartments, &$filters){
$validDepartments = $this->generateValidDepartmentList($departments, $allowedDepartments);
if ($filters != "") $filters .= " and ";
$first = TRUE;
foreach($validDepartments as $department) {
if($first){
$filters .= " ( ";
$first = FALSE;
} else {
$filters .= " or ";
}
$filters .= "ticket.department_id = " . $department;
}
$filters .= ")";
}
private function setAuthorFilter($authors, &$filters){
if($authors !== null){
$first = TRUE;
if ($filters != "") $filters .= " and ";
foreach($authors as $author){
if($first){
$filters .= " ( ";
$first = FALSE;
} else {
$filters .= " or ";
}
if($author['staff']){
$filters .= "ticket.author_staff_id = " . $author['id'];
} else {
$filters .= "ticket.author_id = " . $author['id'];
}
}
$filters .= ")";
}
}
private function setAssignedFilter($assigned, &$filters){
if($assigned !== null){
if ($filters != "") $filters .= " and ";
$key = "";
$assigned == 0 ? $key = "IS NULL" : $key = "IS NOT NULL";
$filters .= "ticket.owner_id " . $key;
}
}
private function setStringFilter($search, &$filters){
$ticketEventTableExists = RedBean::exec("select table_name from information_schema.tables where table_name = 'ticketevent';");
if($search !== null){
if ($filters != "") $filters .= " and ";
$ticketevent = ( $ticketEventTableExists ? " or (ticketevent.type = 'COMMENT' and ticketevent.content LIKE :query)" : "");
$filters .= " (ticket.title LIKE :query or ticket.content LIKE :query or ticket.ticket_number LIKE :query". $ticketevent ." )";
};
}
private function generateValidDepartmentList($departments, $allowedDepartments){
$result = [];
$managedDepartments = [];
if($departments == null) $departments = [];
foreach ($allowedDepartments as $department) {
array_push($managedDepartments,$department['id']);
}
$result = array_intersect($departments,$managedDepartments);
if(empty($result)) $result = $managedDepartments;
$result = array_unique($result);
return $result;
}
//ORDER
private function setQueryOrder($inputs, &$order){
$order = " ORDER BY ";
if(array_key_exists('query',$inputs)) $this->setStringOrder($inputs['query'], $order);
if(array_key_exists('orderBy',$inputs)) $this->setEspecificOrder($inputs['orderBy'], $order);
$order .= "ticket.closed asc, ticket.owner_id asc, ticket.unread_staff asc, ticket.priority desc, ticket.date desc ";
}
private function setEspecificOrder($orderBy, &$order){
if($orderBy !== null){
$orientation = ($orderBy['asc'] ? " asc" : " desc" );
$order .= "ticket." . $orderBy['value'] . $orientation . ",";
};
}
private function setStringOrder($querysearch, &$order){
$ticketEventTableExists = RedBean::exec("select table_name from information_schema.tables where table_name = 'ticketevent';");
if($querysearch !== null){
$ticketeventOrder = ( $ticketEventTableExists ? " CASE WHEN (ticketevent.type = 'COMMENT' and ticketevent.content LIKE :query) THEN ticketevent.content END desc," : "");
$order .= "CASE WHEN (ticket.ticket_number LIKE :query) THEN ticket.ticket_number END desc,CASE WHEN (ticket.title LIKE :query) THEN ticket.title END desc, CASE WHEN ( ticket.content LIKE :query) THEN ticket.content END desc," . $ticketeventOrder ;
}
}
}

View File

@ -277,6 +277,15 @@ class ERRORS {
const INVALID_PRIORITY = 'INVALID_PRIORITY';
const INVALID_PAGE = 'INVALID_PAGE';
const INVALID_QUERY = 'INVALID_QUERY';
const INVALID_TAG_FILTER = 'INVALID_TAG_FILTER';
const INVALID_CLOSED_FILTER = 'INVALID_CLOSED_FILTER';
const INVALID_UNREAD_STAFF_FILTER = 'INVALID_UNREAD_STAFF_FILTER';
const INVALID_PRIORITY_FILTER = 'INVALID_PRIORITY_FILTER';
const INVALID_DATE_RANGE_FILTER = 'INVALID_DATE_RANGE_FILTER';
const INVALID_DEPARTMENT_FILTER = 'INVALID_DEPARTMENT_FILTER';
const INVALID_AUTHOR_FILTER = 'INVALID_AUTHOR_FILTER';
const INVALID_ASSIGNED_FILTER = 'INVALID_ASSIGNED_FILTER';
const INVALID_ORDER_BY = 'INVALID_ORDER_BY';
const INVALID_TOPIC = 'INVALID_TOPIC';
const INVALID_SEARCH = 'INVALID_SEARCH';
const INVALID_ORDER = 'INVALID_ORDER';

View File

@ -0,0 +1,23 @@
<?php
namespace CustomValidations;
use Respect\Validation\Rules\AbstractRule;
class ValidAuthorsId extends AbstractRule {
public function validate($authors) {
if(is_array(json_decode($authors))){
foreach (json_decode($authors) as $authorObject) {
if($authorObject->staff){
$author = \Staff::getDataStore($authorObject->id);
}else{
$author = \User::getDataStore($authorObject->id);
}
if($author->isNull()) return false;
}
return true;
}
return false;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace CustomValidations;
use Respect\Validation\Rules\AbstractRule;
class ValidDateRange extends AbstractRule {
public function validate($dateRange) {
$dateArray = json_decode($dateRange);
if(is_array($dateArray) && count($dateArray) == 2 ){
foreach ($dateArray as $date) {
if (!is_numeric($date)) return false;
}
return $dateArray[0] <= $dateArray[1];
}
return false;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace CustomValidations;
use Respect\Validation\Rules\AbstractRule;
class ValidDepartmentsId extends AbstractRule {
public function validate($departments) {
$DepartmentsList = json_decode($departments);
if(is_array($DepartmentsList)){
foreach ($DepartmentsList as $departmentsId) {
$department = \Department::getDataStore($departmentsId);
if($department->isNull()) return false;
}
return true;
}
return false;
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace CustomValidations;
use Respect\Validation\Rules\AbstractRule;
class ValidOrderBy extends AbstractRule {
public function validate($orderBy) {
if(is_object(json_decode($orderBy))){
$values =["closed","owner_id","unread_staff","priority","date"];
$object = json_decode($orderBy);
if(($object->asc !== 1 && $object->asc !== 0) || !in_array($object->value, $values)) return false;
return true;
}
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace CustomValidations;
use Respect\Validation\Rules\AbstractRule;
class ValidPriorities extends AbstractRule {
public function validate($priorities) {
$PriorityList = json_decode($priorities);
if(is_array($PriorityList)){
foreach (array_unique($PriorityList) as $priorityId) {
if($priorityId != 0 && $priorityId != 1 && $priorityId != 2) return false;
}
return true;
}
return false;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace CustomValidations;
use Respect\Validation\Rules\AbstractRule;
class ValidTagsId extends AbstractRule {
public function validate($tags) {
$listTags = json_decode($tags);
if(is_array($listTags)){
foreach ($listTags as $TagId) {
$tag = \Tag::getDataStore($TagId);
if($tag->isNull()) return false;
}
return true;
}
return false;
}
}

View File

@ -1,13 +1,19 @@
<?php
class Controller {
public static $requestReturnMock = 'mockRequestValue';
const USE_VALUE_RETURN = 'sadf64a5s6d1f5sa';
public static $requestReturnMock = null;
public static $checkUserLoggedReturnMock = true;
public static $isUserSystemEnabledReturnMock = true;
public static function useValueReturn() {
static::$requestReturnMock = self::USE_VALUE_RETURN;
}
public static function request($value) {
if($value === 'staff') return false;
return static::$requestReturnMock;
if(static::$requestReturnMock !== self::USE_VALUE_RETURN) return static::$requestReturnMock;
return $value . '_REQUEST_RESULT';
}
public static function checkUserLogged() {

View File

@ -0,0 +1,292 @@
<?php
// MOCKS
include_once 'tests/__lib__/Mock.php';
include_once 'tests/__mocks__/NullDataStoreMock.php';
include_once 'tests/__mocks__/ResponseMock.php';
include_once 'tests/__mocks__/ControllerMock.php';
include_once 'tests/__mocks__/SessionMock.php';
include_once 'tests/__mocks__/UserMock.php';
include_once 'tests/__mocks__/HashingMock.php';
include_once 'tests/__mocks__/SessionCookieMock.php';
include_once 'tests/__mocks__/RedBeanMock.php';
include_once 'data/ERRORS.php';
use PHPUnit\Framework\TestCase;
use RedBeanPHP\Facade as RedBean;
class SearchControllerTest extends TestCase {
private $searchController;
protected function setUp() {
Session::initStubs();
Response::initStubs();
RedBean::initStubs();
RedBean::setStatics([
'exec' => \Mock::stub()->returns(1)
]);
Controller::$requestReturnMock = null;
$_SERVER['REMOTE_ADDR'] = 'MOCK_REMOTE';
$this->searchController = new SearchController();
}
public function testTagsFilter() {
$this->assertEquals(
$this->searchController->getSQLQuery([
'tags' => []
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) GROUP BY ticket.id'
);
$this->assertEquals(
$this->searchController->getSQLQuery([
'tags' => [0]
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) WHERE ( tag_ticket.tag_id = 0) GROUP BY ticket.id'
);
$this->assertEquals(
$this->searchController->getSQLQuery([
'tags' => [0,1,2]
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) WHERE ( tag_ticket.tag_id = 0 or tag_ticket.tag_id = 1 or tag_ticket.tag_id = 2) GROUP BY ticket.id'
);
}
public function testClosedFilter() {
$this->assertEquals(
$this->searchController->getSQLQuery([
'closed'=> null
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) GROUP BY ticket.id'
);
$this->assertEquals(
$this->searchController->getSQLQuery([
'closed'=> 1
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) WHERE ticket.closed = 1 GROUP BY ticket.id'
);
$this->assertEquals(
$this->searchController->getSQLQuery([
'closed'=> '0'
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) WHERE ticket.closed = 0 GROUP BY ticket.id'
);
}
public function testAssignedFilter(){
$this->assertEquals(
$this->searchController->getSQLQuery([
'assigned'=> null
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) GROUP BY ticket.id'
);
$this->assertEquals(
$this->searchController->getSQLQuery([
'assigned'=> '0'
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) WHERE ticket.owner_id IS NULL GROUP BY ticket.id'
);
$this->assertEquals(
$this->searchController->getSQLQuery([
'assigned'=> 1
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) WHERE ticket.owner_id IS NOT NULL GROUP BY ticket.id'
);
}
public function testUnreadStaffFilter() {
$this->assertEquals(
$this->searchController->getSQLQuery([
'unreadStaff' => null
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) GROUP BY ticket.id'
);
$this->assertEquals(
$this->searchController->getSQLQuery([
'unreadStaff' => '0'
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) WHERE ticket.unread_staff = 0 GROUP BY ticket.id'
);
$this->assertEquals(
$this->searchController->getSQLQuery([
'unreadStaff' => 1
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) WHERE ticket.unread_staff = 1 GROUP BY ticket.id'
);
}
public function testPriorityFilter() {
$this->assertEquals(
$this->searchController->getSQLQuery([
'tags' => []
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) GROUP BY ticket.id'
);
$this->assertEquals(
$this->searchController->getSQLQuery([
'tags' => [1]
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) WHERE ( tag_ticket.tag_id = 1) GROUP BY ticket.id'
);
$this->assertEquals(
$this->searchController->getSQLQuery([
'tags' => [2,3]
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) WHERE ( tag_ticket.tag_id = 2 or tag_ticket.tag_id = 3) GROUP BY ticket.id'
);
$this->assertEquals(
$this->searchController->getSQLQuery([
'tags' => [1,2,3]
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) WHERE ( tag_ticket.tag_id = 1 or tag_ticket.tag_id = 2 or tag_ticket.tag_id = 3) GROUP BY ticket.id'
);
}
public function testdateRangeFilter() {
$this->assertEquals(
$this->searchController->getSQLQuery([
'dateRange' => null
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) GROUP BY ticket.id'
);
$this->assertEquals(
$this->searchController->getSQLQuery([
'dateRange' => [1,2]
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) WHERE (ticket.date >= 1 and ticket.date <= 2) GROUP BY ticket.id'
);
}
public function testDepartmentsFilter() {
$this->assertEquals(
$this->searchController->getSQLQuery([
'departments' => null,
'allowedDepartments' => [
[
'id' => 2
],
[
'id' => 1
],
[
'id' => 3
]
]
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) WHERE ( ticket.department_id = 2 or ticket.department_id = 1 or ticket.department_id = 3) GROUP BY ticket.id'
);
$this->assertEquals(
$this->searchController->getSQLQuery([
'departments' => [1],
'allowedDepartments' => [
[
'id' => 2
],
[
'id' => 1
],
[
'id' => 3
]
]
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) WHERE ( ticket.department_id = 1) GROUP BY ticket.id'
);
$this->assertEquals(
$this->searchController->getSQLQuery([
'departments' => [1,2,3],
'allowedDepartments' => [
[
'id' => 2
],
[
'id' => 1
],
[
'id' => 3
]
]
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) WHERE ( ticket.department_id = 1 or ticket.department_id = 2 or ticket.department_id = 3) GROUP BY ticket.id'
);
}
public function testAuthorsFilter() {
$this->assertEquals(
$this->searchController->getSQLQuery([
'authors' => null
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) GROUP BY ticket.id'
);
$this->assertEquals(
$this->searchController->getSQLQuery([
'authors' => [
[
'id' => 1,
'staff' => 1
],
[
'id' => 2,
'staff' => 0
]
]
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) WHERE ( ticket.author_staff_id = 1 or ticket.author_id = 2) GROUP BY ticket.id'
);
}
public function testQueryFilter() {
$this->assertEquals(
$this->searchController->getSQLQuery([
'query' => null
]),
'FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) GROUP BY ticket.id'
);
$this->assertEquals(
$this->searchController->getSQLQuery([
'query' => 'hello world'
]),
"FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) WHERE (ticket.title LIKE :query or ticket.content LIKE :query or ticket.ticket_number LIKE :query or (ticketevent.type = 'COMMENT' and ticketevent.content LIKE :query) ) GROUP BY ticket.id"
);
}
public function testQueryWithOrder() {
$this->assertEquals(
$this->searchController->getSQLQueryWithOrder([
'page' => 1
]),
"SELECT ticket.id FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) GROUP BY ticket.id ORDER BY ticket.closed asc, ticket.owner_id asc, ticket.unread_staff asc, ticket.priority desc, ticket.date desc LIMIT 10 OFFSET 0"
);
$this->assertEquals(
$this->searchController->getSQLQueryWithOrder([
'page' => 1,
'query' => 'stark'
]),
"SELECT ticket.id FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) WHERE (ticket.title LIKE :query or ticket.content LIKE :query or ticket.ticket_number LIKE :query or (ticketevent.type = 'COMMENT' and ticketevent.content LIKE :query) ) GROUP BY ticket.id ORDER BY CASE WHEN (ticket.ticket_number LIKE :query) THEN ticket.ticket_number END desc,CASE WHEN (ticket.title LIKE :query) THEN ticket.title END desc, CASE WHEN ( ticket.content LIKE :query) THEN ticket.content END desc, CASE WHEN (ticketevent.type = 'COMMENT' and ticketevent.content LIKE :query) THEN ticketevent.content END desc,ticket.closed asc, ticket.owner_id asc, ticket.unread_staff asc, ticket.priority desc, ticket.date desc LIMIT 10 OFFSET 0"
);
$this->assertEquals(
$this->searchController->getSQLQueryWithOrder([
'page' => 1,
'orderBy' => ['value' => 'closed', 'asc' => 1]
]),
"SELECT ticket.id FROM (ticket LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id) GROUP BY ticket.id ORDER BY ticket.closed asc,ticket.closed asc, ticket.owner_id asc, ticket.unread_staff asc, ticket.priority desc, ticket.date desc LIMIT 10 OFFSET 0"
);
}
}

View File

@ -35,9 +35,9 @@ class LoginControllerTest extends TestCase {
public function testShouldCreateSessionAndRespondSuccessIfCredentialsAreValid() {
Session::mockInstanceFunction('sessionExists', \Mock::stub()->returns(false));
Controller::useValueReturn();
$this->loginController->handler();
$this->assertTrue(!!Session::getInstance()->createSession->hasBeenCalledWithArgs('MOCK_ID', false));
$this->assertTrue(Response::get('respondSuccess')->hasBeenCalledWithArgs(array(
'userId' => 'MOCK_ID',

View File

@ -71,4 +71,5 @@ require './ticket/add-tag.rb'
require './ticket/delete-tag.rb'
require './ticket/edit-comment.rb'
require './system/disable-user-system.rb'
require './ticket/search.rb'
# require './system/get-stats.rb'

View File

@ -10,14 +10,18 @@ describe'/staff/get-all' do
(result['status']).should.equal('success')
result['data'][0]['departments'] = result['data'][0]['departments'].sort_by do |department|
department['id'].to_i
end
(result['data'][0]['name']).should.equal('Emilia Clarke')
(result['data'][0]['email']).should.equal('staff@opensupports.com')
(result['data'][0]['profilePic']).should.equal('')
(result['data'][0]['level']).should.equal('3')
(result['data'][0]['departments'][0]['id']).should.equal('2')
(result['data'][0]['departments'][0]['name']).should.equal('useless private deapartment')
(result['data'][0]['departments'][1]['id']).should.equal('1')
(result['data'][0]['departments'][1]['name']).should.equal('Help and Support')
(result['data'][0]['departments'][0]['id']).should.equal('1')
(result['data'][0]['departments'][0]['name']).should.equal('Help and Support')
(result['data'][0]['departments'][1]['id']).should.equal('2')
(result['data'][0]['departments'][1]['name']).should.equal('useless private deapartment')
(result['data'][0]['departments'][2]['id']).should.equal('3')
(result['data'][0]['departments'][2]['name']).should.equal('Suggestions')
(result['data'][0]['assignedTickets']).should.equal(10)

View File

@ -1,7 +1,7 @@
describe '/system/get-settings' do
it 'should return correct values' do
result = request('/system/get-settings')
(result['status']).should.equal('success')
(result['data']['language']).should.equal('en')
(result['data']['departments'][0]['name']).should.equal('Help and Support')

145
tests/ticket/search.rb Normal file
View File

@ -0,0 +1,145 @@
describe '/ticket/search' do
request('/user/logout')
Scripts.login($staff[:email], $staff[:password], true)
it 'should fail if the page is invalid' do
result = request('/ticket/search', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
page: -1
})
(result['status']).should.equal('fail')
(result['message']).should.equal('INVALID_PAGE')
end
it 'should fail if the tags are invalid' do
result = request('/ticket/search', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
page: 1,
tags: "[1,11,111,1111,11111,111111,1111111,11111111]"
})
(result['status']).should.equal('fail')
(result['message']).should.equal('INVALID_TAG_FILTER')
end
it 'should fail if the closed value is invalid' do
result = request('/ticket/search', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
page: 1,
closed: 3
})
(result['status']).should.equal('fail')
(result['message']).should.equal('INVALID_CLOSED_FILTER')
end
it 'should fail if the unreadStaff value is invalid' do
result = request('/ticket/search', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
page: 1,
unreadStaff: 3
})
(result['status']).should.equal('fail')
(result['message']).should.equal('INVALID_UNREAD_STAFF_FILTER')
end
it 'should fail if the priority values are invalid' do
result = request('/ticket/search', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
page: 1,
priority: "[0,1,5,6]"
})
(result['status']).should.equal('fail')
(result['message']).should.equal('INVALID_PRIORITY_FILTER')
end
it 'should fail if the priority' do
result = request('/ticket/search', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
page: 1,
priority: "[0,1,),hi]"
})
(result['status']).should.equal('fail')
(result['message']).should.equal('INVALID_PRIORITY_FILTER')
end
it 'should fail if the dateRange values are invalid' do
result = request('/ticket/search', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
page: 1,
dateRange: "[11,69,()) ]"
})
(result['status']).should.equal('fail')
(result['message']).should.equal('INVALID_DATE_RANGE_FILTER')
end
it 'should fail if the departments are invalid' do
result = request('/ticket/search', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
page: 1,
departments: "[-1,-2,99]"
})
(result['status']).should.equal('fail')
(result['message']).should.equal('INVALID_DEPARTMENT_FILTER')
end
it 'should fail if the authors are invalid' do
result = request('/ticket/search', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
page: 1,
authors: "[{id:30001, staff: 1},{id:30,staff: 3}]"
})
(result['status']).should.equal('fail')
(result['message']).should.equal('INVALID_AUTHOR_FILTER')
result = request('/ticket/search', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
page: 1,
authors: "[{id:'delete all)', staff: 1},{id:30,staff: 3}]"
})
(result['status']).should.equal('fail')
(result['message']).should.equal('INVALID_AUTHOR_FILTER')
end
it 'should fail if the assigned value is invalid' do
result = request('/ticket/search', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
page: 1,
assigned: 3
})
(result['status']).should.equal('fail')
(result['message']).should.equal('INVALID_ASSIGNED_FILTER')
end
it 'should fail if the assigned value is invalid' do
result = request('/ticket/search', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
page: 1,
assigned: 11113
})
(result['status']).should.equal('fail')
(result['message']).should.equal('INVALID_ASSIGNED_FILTER')
end
it 'should fail if the orderBy values are invalid' do
result = request('/ticket/search', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
page: 1,
orderBy: "{value: 'closeddd', asc: 11}"
})
(result['status']).should.equal('fail')
(result['message']).should.equal('INVALID_ORDER_BY')
end
end