Added a ticket queues object to organize tickets; added ability to change a ticket's queue
This commit is contained in:
parent
15ee9b78a3
commit
80a12a86ef
@ -1,12 +1,41 @@
|
|||||||
$(document).ready ->
|
$(document).ready ->
|
||||||
console.log('Hello, world!')
|
$('.ticket-queue').on('click', (e) ->
|
||||||
|
handleQueueClick(e)
|
||||||
$('.ticket-severity').on('click', handleAttributeClick('severity'))
|
)
|
||||||
$('.ticket-status').on('click', handleAttributeClick('status'))
|
$('.ticket-severity').on('click', (e) ->
|
||||||
|
handleAttributeClick(e, 'severity')
|
||||||
|
)
|
||||||
|
$('.ticket-status').on('click', (e) ->
|
||||||
|
handleAttributeClick(e, 'status')
|
||||||
|
)
|
||||||
|
|
||||||
validOptions =
|
validOptions =
|
||||||
'severity' = ['low', 'medium', 'high'],
|
'severity': ['low', 'medium', 'high'],
|
||||||
'status' = ['open', 'closed', 'parked']
|
'status': ['open', 'closed', 'parked']
|
||||||
|
|
||||||
|
handleQueueClick = (e, fail = false) ->
|
||||||
|
newQueueId = prompt('Set queue ID:', $('.ticket-queue').data('id'))
|
||||||
|
|
||||||
|
if (newQueueId != null) and (newQueueId != '')
|
||||||
|
if (true)
|
||||||
|
console.log('Setting queue ID to ' + newQueueId)
|
||||||
|
editLink = $('#ticketEditLink').attr('href') + '/queue_id'
|
||||||
|
console.log('Sending data to ' + editLink)
|
||||||
|
$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: editLink,
|
||||||
|
data:
|
||||||
|
'queue_id': newQueueId,
|
||||||
|
dataType: 'json',
|
||||||
|
success: (result) ->
|
||||||
|
$('.ticket-queue').data('id', newQueueId)
|
||||||
|
$('.ticket-queue > span').text(result.queue_name)
|
||||||
|
updateTicketModified(result.updated_at)
|
||||||
|
console.log('Ticket updated successfully.')
|
||||||
|
})
|
||||||
|
else
|
||||||
|
console.log('Invalid queue ID entered')
|
||||||
|
handleQueueClick(e, 'Invalid queue ID entered; you must enter a number.')
|
||||||
|
|
||||||
handleAttributeClick = (e, attr, fail = false) ->
|
handleAttributeClick = (e, attr, fail = false) ->
|
||||||
newValue = prompt('Set ticket ' + attr + ':', $('.ticket-' + attr + ' > span').text())
|
newValue = prompt('Set ticket ' + attr + ':', $('.ticket-' + attr + ' > span').text())
|
||||||
@ -16,12 +45,13 @@ handleAttributeClick = (e, attr, fail = false) ->
|
|||||||
if (newValue in validOptions[attr])
|
if (newValue in validOptions[attr])
|
||||||
console.log('Setting ' + attr + ' to ' + newValue)
|
console.log('Setting ' + attr + ' to ' + newValue)
|
||||||
editLink = $('#ticketEditLink').attr('href') + '/' + attr
|
editLink = $('#ticketEditLink').attr('href') + '/' + attr
|
||||||
|
postData = {}
|
||||||
|
postData[attr] = newValue
|
||||||
console.log('Sending data to ' + editLink)
|
console.log('Sending data to ' + editLink)
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "POST",
|
type: "POST",
|
||||||
url: editLink,
|
url: editLink,
|
||||||
data:
|
data: postData,
|
||||||
attr: newValue,
|
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: (result) ->
|
success: (result) ->
|
||||||
newValue = newValue.charAt(0).toUpperCase() + newValue.slice(1)
|
newValue = newValue.charAt(0).toUpperCase() + newValue.slice(1)
|
||||||
|
@ -81,12 +81,16 @@ input[type="submit"].button-primary
|
|||||||
height: 250px
|
height: 250px
|
||||||
min-height: 100px
|
min-height: 100px
|
||||||
|
|
||||||
|
#queue-header,
|
||||||
#ticket-header
|
#ticket-header
|
||||||
margin-bottom: 15px
|
margin-bottom: 15px
|
||||||
|
|
||||||
|
.queue-title,
|
||||||
.ticket-title
|
.ticket-title
|
||||||
margin-bottom: 5px
|
margin-bottom: 5px
|
||||||
|
|
||||||
|
.queue-created,
|
||||||
|
.queue-updated,
|
||||||
.ticket-created,
|
.ticket-created,
|
||||||
.ticket-updated
|
.ticket-updated
|
||||||
margin-bottom: 3px
|
margin-bottom: 3px
|
||||||
@ -94,6 +98,7 @@ input[type="submit"].button-primary
|
|||||||
font-size: 1.5rem
|
font-size: 1.5rem
|
||||||
font-style: italic
|
font-style: italic
|
||||||
|
|
||||||
|
#queue-description,
|
||||||
#ticket-body
|
#ticket-body
|
||||||
p:last-child
|
p:last-child
|
||||||
margin-bottom: 5px
|
margin-bottom: 5px
|
||||||
@ -141,6 +146,7 @@ input[type="submit"].button-primary
|
|||||||
margin-right: 5px
|
margin-right: 5px
|
||||||
font-size: 2rem
|
font-size: 2rem
|
||||||
|
|
||||||
|
.ticket-queue,
|
||||||
.ticket-severity,
|
.ticket-severity,
|
||||||
.ticket-status
|
.ticket-status
|
||||||
transition: all 230ms ease-in-out
|
transition: all 230ms ease-in-out
|
||||||
|
35
db/migrations/20221204223428_add_queue_table.php
Normal file
35
db/migrations/20221204223428_add_queue_table.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class AddQueueTable extends AbstractMigration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Change Method.
|
||||||
|
*
|
||||||
|
* Write your reversible migrations using this method.
|
||||||
|
*
|
||||||
|
* More information on writing migrations is available here:
|
||||||
|
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
|
||||||
|
*
|
||||||
|
* Remember to call "create()" or "update()" and NOT "save()" when working
|
||||||
|
* with the Table class.
|
||||||
|
*/
|
||||||
|
public function change()
|
||||||
|
{
|
||||||
|
// Create table for Queue objects
|
||||||
|
$table = $this->table('queues');
|
||||||
|
$table->addColumn('title', 'string', ['null' => false])
|
||||||
|
->addColumn('description', 'text', ['null' => false])
|
||||||
|
->addTimestamps()
|
||||||
|
->addIndex(['title'])
|
||||||
|
->create();
|
||||||
|
|
||||||
|
// Update tickets table to have a Queue ID field
|
||||||
|
$tickets = $this->table('tickets')
|
||||||
|
->addColumn('queue_id', 'integer', ['null' => false])
|
||||||
|
->addForeignKey('queue_id', 'queues', 'id', ['delete' => 'CASCADE', 'update' => 'CASCADE'])
|
||||||
|
->update();
|
||||||
|
}
|
||||||
|
}
|
44
src/Controllers/QueueController.php
Normal file
44
src/Controllers/QueueController.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace BitGoblin\Goliath\Controllers;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
|
use Slim\Routing\RouteContext;
|
||||||
|
use Slim\Views\Twig;
|
||||||
|
use BitGoblin\Goliath\Models\Queue;
|
||||||
|
|
||||||
|
class QueueController extends Controller {
|
||||||
|
|
||||||
|
public function getView(Request $request, Response $response, array $args): Response {
|
||||||
|
$queue = Queue::where('id', $args['queue_id'])->first();
|
||||||
|
|
||||||
|
$view = Twig::fromRequest($request);
|
||||||
|
return $view->render($response, 'queue/view.twig', [
|
||||||
|
'queue' => $queue,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreate(Request $request, Response $response): Response {
|
||||||
|
$view = Twig::fromRequest($request);
|
||||||
|
return $view->render($response, 'queue/create.twig');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function postCreate(Request $request, Response $response): Response {
|
||||||
|
$params = (array)$request->getParsedBody();
|
||||||
|
|
||||||
|
$queue = new Queue;
|
||||||
|
$queue->title = $params['queue_title'];
|
||||||
|
$queue->description = $params['queue_description'];
|
||||||
|
|
||||||
|
$queue->save();
|
||||||
|
|
||||||
|
// redirect the user back to the home page
|
||||||
|
$routeContext = RouteContext::fromRequest($request);
|
||||||
|
$routeParser = $routeContext->getRouteParser();
|
||||||
|
return $response
|
||||||
|
->withHeader('Location', $routeParser->urlFor('queue.view', ['queue_id' => $queue->id]))
|
||||||
|
->withStatus(302);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -81,11 +81,17 @@ class TicketController extends Controller {
|
|||||||
// save updated ticket
|
// save updated ticket
|
||||||
$ticket->save();
|
$ticket->save();
|
||||||
|
|
||||||
// return a response
|
// build JSON response
|
||||||
$response->getBody()->write(json_encode([
|
$jsonResponse = [
|
||||||
'result' => 'success',
|
'result' => 'success',
|
||||||
'updated_at' => $ticket->formatUpdatedAt(),
|
'updated_at' => $ticket->formatUpdatedAt(),
|
||||||
]));
|
];
|
||||||
|
if ($attr == 'queue_id') {
|
||||||
|
$jsonResponse['queue_name'] = $ticket->queue->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return a response
|
||||||
|
$response->getBody()->write(json_encode($jsonResponse));
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
36
src/Models/Queue.php
Normal file
36
src/Models/Queue.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace BitGoblin\Goliath\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use League\CommonMark\CommonMarkConverter;
|
||||||
|
|
||||||
|
class Queue extends Model {
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'title',
|
||||||
|
'description',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function tickets() {
|
||||||
|
return $this->hasMany(Ticket::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): string {
|
||||||
|
$converter = new CommonMarkConverter([
|
||||||
|
'html_input' => 'strip',
|
||||||
|
'allow_unsafe_links' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $converter->convert($this->description);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function formatCreatedAt(): string {
|
||||||
|
return date_format(date_create($this->created_at), "F jS\\, Y \\a\\t g:i:s a");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function formatUpdatedAt(): string {
|
||||||
|
return date_format(date_create($this->updated_at), "F jS\\, Y \\a\\t g:i:s a");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -14,6 +14,14 @@ class Ticket extends Model {
|
|||||||
'due_at',
|
'due_at',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public function queue() {
|
||||||
|
return $this->belongsTo(Queue::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function comments() {
|
||||||
|
return $this->hasMany(Comment::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function render(): string {
|
public function render(): string {
|
||||||
$converter = new CommonMarkConverter([
|
$converter = new CommonMarkConverter([
|
||||||
'html_input' => 'strip',
|
'html_input' => 'strip',
|
||||||
@ -23,10 +31,6 @@ class Ticket extends Model {
|
|||||||
return $converter->convert($this->body);
|
return $converter->convert($this->body);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function comments() {
|
|
||||||
return $this->hasMany(Comment::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function formatCreatedAt(): string {
|
public function formatCreatedAt(): string {
|
||||||
return date_format(date_create($this->created_at), "F jS\\, Y \\a\\t g:i:s a");
|
return date_format(date_create($this->created_at), "F jS\\, Y \\a\\t g:i:s a");
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,13 @@ use Slim\Views\Twig;
|
|||||||
// index GET route - this page should welcome the user and direct them to the available actions
|
// index GET route - this page should welcome the user and direct them to the available actions
|
||||||
$app->get('/', '\\BitGoblin\\Goliath\\Controllers\\HomeController:getIndex')->setName('index');
|
$app->get('/', '\\BitGoblin\\Goliath\\Controllers\\HomeController:getIndex')->setName('index');
|
||||||
|
|
||||||
|
// /queue/create routes - allows a user to fill out a form to create a new queue
|
||||||
|
$app->get('/queue/create', '\\BitGoblin\\Goliath\\Controllers\\QueueController:getCreate')->setName('queue.create');
|
||||||
|
$app->post('/queue/create', '\\BitGoblin\\Goliath\\Controllers\\QueueController:postCreate');
|
||||||
|
|
||||||
|
// /queue/id route - displays queue info to user
|
||||||
|
$app->get('/queue/{queue_id}', '\\BitGoblin\\Goliath\\Controllers\QueueController:getView')->setName('queue.view');
|
||||||
|
|
||||||
// /ticket/create routes - allows a user to fill out a form to create a ticket
|
// /ticket/create routes - allows a user to fill out a form to create a ticket
|
||||||
$app->get('/ticket/create', '\\BitGoblin\\Goliath\\Controllers\\TicketController:getCreate')->setName('ticket.create');
|
$app->get('/ticket/create', '\\BitGoblin\\Goliath\\Controllers\\TicketController:getCreate')->setName('ticket.create');
|
||||||
$app->post('/ticket/create', '\\BitGoblin\\Goliath\\Controllers\\TicketController:postCreate');
|
$app->post('/ticket/create', '\\BitGoblin\\Goliath\\Controllers\\TicketController:postCreate');
|
||||||
|
@ -16,7 +16,8 @@
|
|||||||
<ul class="nav-menu">
|
<ul class="nav-menu">
|
||||||
<li>Goliath</li>
|
<li>Goliath</li>
|
||||||
<li class="nav-link"><a href="{{ url_for('index') }}">Home</a></li>
|
<li class="nav-link"><a href="{{ url_for('index') }}">Home</a></li>
|
||||||
<li class="nav-link"><a href="{{ url_for('ticket.create') }}">Create</a></li>
|
<li class="nav-link"><a href="{{ url_for('queue.create') }}">New Queue</a></li>
|
||||||
|
<li class="nav-link"><a href="{{ url_for('ticket.create') }}">New Ticket</a></li>
|
||||||
<li class="nav-link"><a href="/search">Search</a></li>
|
<li class="nav-link"><a href="/search">Search</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
29
views/queue/create.twig
Normal file
29
views/queue/create.twig
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{% extends 'layout.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Create New Queue{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="columns twelve">
|
||||||
|
<h1>Create new queue</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="columns twelve">
|
||||||
|
<form id="queue-form" action="/queue/create" method="POST" class="u-full-width">
|
||||||
|
<div class="row">
|
||||||
|
<div class="twelve columns">
|
||||||
|
<label for="queue_title">Title</label>
|
||||||
|
<input id="queue_title" class="u-full-width" type="text" placeholder="My new queue" name="queue_title">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label for="queue_description">Description</label>
|
||||||
|
<textarea id="queue_description" class="u-full-width" placeholder="Explain what this queue is about..." name="queue_description"></textarea>
|
||||||
|
|
||||||
|
<input class="button-primary u-full-width" type="submit" value="Submit">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
44
views/queue/view.twig
Normal file
44
views/queue/view.twig
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{% extends 'layout.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Ticket Queue: {{ queue.title }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<!-- queue content -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="twelve columns">
|
||||||
|
<div id="queue-header" class="row">
|
||||||
|
<div class="columns twelve">
|
||||||
|
<h1 class="queue-title">{{ queue.title }}</h1>
|
||||||
|
<h4 class="queue-created">Created at: <span>{{ queue.formatCreatedAt() }}</span></h4>
|
||||||
|
<h4 class="queue-updated">Last updated at: <span>{{ queue.formatUpdatedAt() }}</span></h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="queue-description" class="row">
|
||||||
|
<div class="columns twelve">
|
||||||
|
{{ queue.render() | raw }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<!-- queue tickets -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="twelve columns">
|
||||||
|
<ul id="queue-list" class="row">
|
||||||
|
<h3>Tickets in this queue:</h3>
|
||||||
|
{% if queue.tickets | length > 0 %}
|
||||||
|
{% for ticket in queue.tickets %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('ticket.view', { ticket_id: ticket.id }) }}">{{ ticket.title }}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<p>There are no tickets in this queue.</p>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -29,6 +29,9 @@
|
|||||||
<li><a id="ticketEditLink" href="{{ url_for('ticket.edit', {ticket_id: ticket.id}) }}"><i class="fa-solid fa-pen-to-square"></i>Edit</a></li><li><a href="{{ url_for('ticket.delete', {ticket_id: ticket.id}) }}"><i class="fa-solid fa-trash"></i>Delete</a></li>
|
<li><a id="ticketEditLink" href="{{ url_for('ticket.edit', {ticket_id: ticket.id}) }}"><i class="fa-solid fa-pen-to-square"></i>Edit</a></li><li><a href="{{ url_for('ticket.delete', {ticket_id: ticket.id}) }}"><i class="fa-solid fa-trash"></i>Delete</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="ticket-queue" data-id="{{ ticket.queue.id }}">
|
||||||
|
Queue: <span>{{ ticket.queue.title }}</span>
|
||||||
|
</li>
|
||||||
<li class="ticket-severity">
|
<li class="ticket-severity">
|
||||||
Severity: <span>{{ ticket.severity | capitalize }}</span>
|
Severity: <span>{{ ticket.severity | capitalize }}</span>
|
||||||
</li>
|
</li>
|
||||||
|
Loading…
Reference in New Issue
Block a user