Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/docker-staging-deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -302,4 +302,4 @@ jobs:
echo "Deployment to staging failed!"
echo "Check logs for details."
echo "If rollback occurred, the previous version should still be running."
echo "======================================"
echo "======================================"
24 changes: 23 additions & 1 deletion app/Core/Middleware/StartSession.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ protected function handleRequestWhileBlocking(IncomingRequest $request, $session
$holdLockFor = $this->calculateLockDuration($request); // Hold lock for x seconds after acquiring

// Maximum time to wait for acquiring the lock if already held
$maxWaitForLock = 5; // Wait for up to y seconds to acquire the lock
$maxWaitForLock = 1; // Wait for up to y seconds to acquire the lock

$lock = $this->cache($this->manager->blockDriver())
->lock('session:'.$session->getId(), $holdLockFor)
Expand Down Expand Up @@ -308,6 +308,9 @@ protected function saveSession(IncomingRequest $request)

protected function shouldLockSession(IncomingRequest $request)
{
if ($this->isKanbanMoveApiRequest($request)) {
return false;
}

if (
$request->isApiOrCronRequest() === false &&
Expand All @@ -319,6 +322,25 @@ protected function shouldLockSession(IncomingRequest $request)
return false;
}

/**
* Kanban move requests can become high-frequency and should not wait on session lock contention.
*/
protected function isKanbanMoveApiRequest(IncomingRequest $request): bool
{
if (! $request->isApiOrCronRequest() || ! $request->isMethod('POST')) {
return false;
}

$path = trim((string) $request->path(), '/');
if ($path !== 'api/tickets') {
return false;
}

$action = (string) ($request->input('action') ?? '');

return in_array($action, ['kanbanMoveDelta', 'kanbanSort'], true);
}

/**
* Get the session lifetime in seconds.
*
Expand Down
67 changes: 67 additions & 0 deletions app/Domain/Tickets/Controllers/KanbanPage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

namespace Leantime\Domain\Tickets\Controllers;

use Leantime\Core\Controller\Controller;
use Leantime\Domain\Projects\Services\Projects as ProjectService;
use Leantime\Domain\Tickets\Services\Tickets as TicketService;
use Leantime\Domain\Timesheets\Services\Timesheets as TimesheetService;
use Symfony\Component\HttpFoundation\Response;

class KanbanPage extends Controller
{
private ProjectService $projectService;

private TicketService $ticketService;

private TimesheetService $timesheetService;

public function init(
ProjectService $projectService,
TicketService $ticketService,
TimesheetService $timesheetService
): void {
$this->projectService = $projectService;
$this->ticketService = $ticketService;
$this->timesheetService = $timesheetService;
}

public function load(array $params): Response
{
$status = (int) ($params['status'] ?? -999);
$limit = (int) ($params['limit'] ?? $this->ticketService->getKanbanPageSize());

if (! array_key_exists($status, $this->ticketService->getKanbanColumns())) {
return new Response('', 400);
}

$limit = min(max(1, $limit), 100);
$tickets = $this->ticketService->getKanbanTicketsForStatus($params, $status, $limit);

$this->tpl->assign('allKanbanColumns', $this->ticketService->getKanbanColumns());
$this->tpl->assign('onTheClock', $this->timesheetService->isClocked(session('userdata.id')));
$this->tpl->assign('todoTypeIcons', $this->ticketService->getTypeIcons());
$this->tpl->assign('efforts', $this->ticketService->getEffortLabels());
$this->tpl->assign('priorities', $this->ticketService->getPriorityLabels());
$this->tpl->assign('users', $this->projectService->getUsersAssignedToProject(session('currentProject')));
$this->tpl->assign('milestones', $this->ticketService->getAllMilestones([
'sprint' => '',
'type' => 'milestone',
'currentProject' => session('currentProject'),
]));

$tpl = $this->tpl;
$key = $status;
$todoTypeIcons = $this->ticketService->getTypeIcons();
$efforts = $this->ticketService->getEffortLabels();
$priorities = $this->ticketService->getPriorityLabels();

ob_start();
foreach ($tickets as $row) {
include __DIR__.'/../Templates/partials/kanbanTicketCard.tpl.php';
}
$html = ob_get_clean();

return new Response($html, 200);
}
}
Loading
Loading