Add support for executing a scheduled task right now
This commit is contained in:
parent
f33d0b1d72
commit
c1ee0ac4f8
13 changed files with 158 additions and 157 deletions
|
@ -15,16 +15,6 @@ interface ScheduleRepositoryInterface extends RepositoryInterface
|
||||||
*/
|
*/
|
||||||
public function findServerSchedules(int $server): Collection;
|
public function findServerSchedules(int $server): Collection;
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the tasks relationship onto the Schedule module if they are not
|
|
||||||
* already present.
|
|
||||||
*
|
|
||||||
* @param \Pterodactyl\Models\Schedule $schedule
|
|
||||||
* @param bool $refresh
|
|
||||||
* @return \Pterodactyl\Models\Schedule
|
|
||||||
*/
|
|
||||||
public function loadTasks(Schedule $schedule, bool $refresh = false): Schedule;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a schedule model with all of the associated tasks as a relationship.
|
* Return a schedule model with all of the associated tasks as a relationship.
|
||||||
*
|
*
|
||||||
|
|
|
@ -10,15 +10,19 @@ use Pterodactyl\Models\Server;
|
||||||
use Pterodactyl\Models\Schedule;
|
use Pterodactyl\Models\Schedule;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Pterodactyl\Helpers\Utilities;
|
use Pterodactyl\Helpers\Utilities;
|
||||||
|
use Pterodactyl\Jobs\Schedule\RunTaskJob;
|
||||||
use Pterodactyl\Exceptions\DisplayException;
|
use Pterodactyl\Exceptions\DisplayException;
|
||||||
use Pterodactyl\Repositories\Eloquent\ScheduleRepository;
|
use Pterodactyl\Repositories\Eloquent\ScheduleRepository;
|
||||||
|
use Pterodactyl\Services\Schedules\ProcessScheduleService;
|
||||||
use Pterodactyl\Transformers\Api\Client\ScheduleTransformer;
|
use Pterodactyl\Transformers\Api\Client\ScheduleTransformer;
|
||||||
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
|
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
|
||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\ViewScheduleRequest;
|
use Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\ViewScheduleRequest;
|
||||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\StoreScheduleRequest;
|
use Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\StoreScheduleRequest;
|
||||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\DeleteScheduleRequest;
|
use Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\DeleteScheduleRequest;
|
||||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\UpdateScheduleRequest;
|
use Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\UpdateScheduleRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\TriggerScheduleRequest;
|
||||||
|
|
||||||
class ScheduleController extends ClientApiController
|
class ScheduleController extends ClientApiController
|
||||||
{
|
{
|
||||||
|
@ -27,16 +31,23 @@ class ScheduleController extends ClientApiController
|
||||||
*/
|
*/
|
||||||
private $repository;
|
private $repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Pterodactyl\Services\Schedules\ProcessScheduleService
|
||||||
|
*/
|
||||||
|
private $service;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ScheduleController constructor.
|
* ScheduleController constructor.
|
||||||
*
|
*
|
||||||
* @param \Pterodactyl\Repositories\Eloquent\ScheduleRepository $repository
|
* @param \Pterodactyl\Repositories\Eloquent\ScheduleRepository $repository
|
||||||
|
* @param \Pterodactyl\Services\Schedules\ProcessScheduleService $service
|
||||||
*/
|
*/
|
||||||
public function __construct(ScheduleRepository $repository)
|
public function __construct(ScheduleRepository $repository, ProcessScheduleService $service)
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
||||||
$this->repository = $repository;
|
$this->repository = $repository;
|
||||||
|
$this->service = $service;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -147,6 +158,30 @@ class ScheduleController extends ClientApiController
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a given schedule immediately rather than waiting on it's normally scheduled time
|
||||||
|
* to pass. This does not care about the schedule state.
|
||||||
|
*
|
||||||
|
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\TriggerScheduleRequest $request
|
||||||
|
* @param \Pterodactyl\Models\Server $server
|
||||||
|
* @param \Pterodactyl\Models\Schedule $schedule
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*
|
||||||
|
* @throws \Throwable
|
||||||
|
*/
|
||||||
|
public function execute(TriggerScheduleRequest $request, Server $server, Schedule $schedule)
|
||||||
|
{
|
||||||
|
if (!$schedule->is_active) {
|
||||||
|
throw new BadRequestHttpException(
|
||||||
|
'Cannot trigger schedule exection for a schedule that is not currently active.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->service->handle($schedule, true);
|
||||||
|
|
||||||
|
return new JsonResponse([], JsonResponse::HTTP_ACCEPTED);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a schedule and it's associated tasks.
|
* Deletes a schedule and it's associated tasks.
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Schedules;
|
||||||
|
|
||||||
|
use Pterodactyl\Models\Permission;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class TriggerScheduleRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function permission(): string
|
||||||
|
{
|
||||||
|
return Permission::ACTION_SCHEDULE_UPDATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,15 +42,13 @@ class RunTaskJob extends Job implements ShouldQueue
|
||||||
* @param \Pterodactyl\Repositories\Wings\DaemonCommandRepository $commandRepository
|
* @param \Pterodactyl\Repositories\Wings\DaemonCommandRepository $commandRepository
|
||||||
* @param \Pterodactyl\Services\Backups\InitiateBackupService $backupService
|
* @param \Pterodactyl\Services\Backups\InitiateBackupService $backupService
|
||||||
* @param \Pterodactyl\Repositories\Wings\DaemonPowerRepository $powerRepository
|
* @param \Pterodactyl\Repositories\Wings\DaemonPowerRepository $powerRepository
|
||||||
* @param \Pterodactyl\Repositories\Eloquent\TaskRepository $taskRepository
|
|
||||||
*
|
*
|
||||||
* @throws \Throwable
|
* @throws \Throwable
|
||||||
*/
|
*/
|
||||||
public function handle(
|
public function handle(
|
||||||
DaemonCommandRepository $commandRepository,
|
DaemonCommandRepository $commandRepository,
|
||||||
InitiateBackupService $backupService,
|
InitiateBackupService $backupService,
|
||||||
DaemonPowerRepository $powerRepository,
|
DaemonPowerRepository $powerRepository
|
||||||
TaskRepository $taskRepository
|
|
||||||
) {
|
) {
|
||||||
// Do not process a task that is not set to active.
|
// Do not process a task that is not set to active.
|
||||||
if (! $this->task->schedule->is_active) {
|
if (! $this->task->schedule->is_active) {
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Models;
|
namespace Pterodactyl\Models;
|
||||||
|
|
||||||
|
use Cron\CronExpression;
|
||||||
|
use Carbon\CarbonImmutable;
|
||||||
use Illuminate\Container\Container;
|
use Illuminate\Container\Container;
|
||||||
use Pterodactyl\Contracts\Extensions\HashidsInterface;
|
use Pterodactyl\Contracts\Extensions\HashidsInterface;
|
||||||
|
|
||||||
|
@ -114,6 +116,20 @@ class Schedule extends Model
|
||||||
'next_run_at' => 'nullable|date',
|
'next_run_at' => 'nullable|date',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the schedule's execution crontab entry as a string.
|
||||||
|
*
|
||||||
|
* @return \Carbon\CarbonImmutable
|
||||||
|
*/
|
||||||
|
public function getNextRunDate()
|
||||||
|
{
|
||||||
|
$formatted = sprintf('%s %s %s * %s', $this->cron_minute, $this->cron_hour, $this->cron_day_of_month, $this->cron_day_of_week);
|
||||||
|
|
||||||
|
return CarbonImmutable::createFromTimestamp(
|
||||||
|
CronExpression::factory($formatted)->getNextRunDate()->getTimestamp()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a hashid encoded string to represent the ID of the schedule.
|
* Return a hashid encoded string to represent the ID of the schedule.
|
||||||
*
|
*
|
||||||
|
|
|
@ -31,23 +31,6 @@ class ScheduleRepository extends EloquentRepository implements ScheduleRepositor
|
||||||
return $this->getBuilder()->withCount('tasks')->where('server_id', '=', $server)->get($this->getColumns());
|
return $this->getBuilder()->withCount('tasks')->where('server_id', '=', $server)->get($this->getColumns());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the tasks relationship onto the Schedule module if they are not
|
|
||||||
* already present.
|
|
||||||
*
|
|
||||||
* @param \Pterodactyl\Models\Schedule $schedule
|
|
||||||
* @param bool $refresh
|
|
||||||
* @return \Pterodactyl\Models\Schedule
|
|
||||||
*/
|
|
||||||
public function loadTasks(Schedule $schedule, bool $refresh = false): Schedule
|
|
||||||
{
|
|
||||||
if (! $schedule->relationLoaded('tasks') || $refresh) {
|
|
||||||
$schedule->load('tasks');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $schedule;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a schedule model with all of the associated tasks as a relationship.
|
* Return a schedule model with all of the associated tasks as a relationship.
|
||||||
*
|
*
|
||||||
|
|
|
@ -2,12 +2,10 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Services\Schedules;
|
namespace Pterodactyl\Services\Schedules;
|
||||||
|
|
||||||
use Cron\CronExpression;
|
|
||||||
use Pterodactyl\Models\Schedule;
|
use Pterodactyl\Models\Schedule;
|
||||||
use Illuminate\Contracts\Bus\Dispatcher;
|
use Illuminate\Contracts\Bus\Dispatcher;
|
||||||
use Pterodactyl\Jobs\Schedule\RunTaskJob;
|
use Pterodactyl\Jobs\Schedule\RunTaskJob;
|
||||||
use Pterodactyl\Contracts\Repository\TaskRepositoryInterface;
|
use Illuminate\Database\ConnectionInterface;
|
||||||
use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface;
|
|
||||||
|
|
||||||
class ProcessScheduleService
|
class ProcessScheduleService
|
||||||
{
|
{
|
||||||
|
@ -17,62 +15,45 @@ class ProcessScheduleService
|
||||||
private $dispatcher;
|
private $dispatcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface
|
* @var \Illuminate\Database\ConnectionInterface
|
||||||
*/
|
*/
|
||||||
private $scheduleRepository;
|
private $connection;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \Pterodactyl\Contracts\Repository\TaskRepositoryInterface
|
|
||||||
*/
|
|
||||||
private $taskRepository;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ProcessScheduleService constructor.
|
* ProcessScheduleService constructor.
|
||||||
*
|
*
|
||||||
|
* @param \Illuminate\Database\ConnectionInterface $connection
|
||||||
* @param \Illuminate\Contracts\Bus\Dispatcher $dispatcher
|
* @param \Illuminate\Contracts\Bus\Dispatcher $dispatcher
|
||||||
* @param \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface $scheduleRepository
|
|
||||||
* @param \Pterodactyl\Contracts\Repository\TaskRepositoryInterface $taskRepository
|
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(ConnectionInterface $connection, Dispatcher $dispatcher)
|
||||||
Dispatcher $dispatcher,
|
{
|
||||||
ScheduleRepositoryInterface $scheduleRepository,
|
|
||||||
TaskRepositoryInterface $taskRepository
|
|
||||||
) {
|
|
||||||
$this->dispatcher = $dispatcher;
|
$this->dispatcher = $dispatcher;
|
||||||
$this->scheduleRepository = $scheduleRepository;
|
$this->connection = $connection;
|
||||||
$this->taskRepository = $taskRepository;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process a schedule and push the first task onto the queue worker.
|
* Process a schedule and push the first task onto the queue worker.
|
||||||
*
|
*
|
||||||
* @param \Pterodactyl\Models\Schedule $schedule
|
* @param \Pterodactyl\Models\Schedule $schedule
|
||||||
|
* @param bool $now
|
||||||
*
|
*
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
* @throws \Throwable
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
*/
|
||||||
public function handle(Schedule $schedule)
|
public function handle(Schedule $schedule, bool $now = false)
|
||||||
{
|
{
|
||||||
$this->scheduleRepository->loadTasks($schedule);
|
|
||||||
|
|
||||||
/** @var \Pterodactyl\Models\Task $task */
|
/** @var \Pterodactyl\Models\Task $task */
|
||||||
$task = $schedule->getRelation('tasks')->where('sequence_id', 1)->first();
|
$task = $schedule->tasks()->where('sequence_id', 1)->firstOrFail();
|
||||||
|
|
||||||
$formattedCron = sprintf('%s %s %s * %s',
|
$this->connection->transaction(function () use ($schedule, $task) {
|
||||||
$schedule->cron_minute,
|
$schedule->forceFill([
|
||||||
$schedule->cron_hour,
|
'is_processing' => true,
|
||||||
$schedule->cron_day_of_month,
|
'next_run_at' => $schedule->getNextRunDate(),
|
||||||
$schedule->cron_day_of_week
|
])->saveOrFail();
|
||||||
);
|
|
||||||
|
|
||||||
$this->scheduleRepository->update($schedule->id, [
|
$task->update(['is_queued' => true]);
|
||||||
'is_processing' => true,
|
});
|
||||||
'next_run_at' => CronExpression::factory($formattedCron)->getNextRunDate(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->taskRepository->update($task->id, ['is_queued' => true]);
|
$this->dispatcher->{$now ? 'dispatchNow' : 'dispatch'}(
|
||||||
|
|
||||||
$this->dispatcher->dispatch(
|
|
||||||
(new RunTaskJob($task))->delay($task->time_offset)
|
(new RunTaskJob($task))->delay($task->time_offset)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
import http from '@/api/http';
|
||||||
|
|
||||||
|
export default async (server: string, schedule: number): Promise<void> =>
|
||||||
|
await http.post(`/api/client/servers/${server}/schedules/${schedule}/execute`);
|
|
@ -0,0 +1,48 @@
|
||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
import Button from '@/components/elements/Button';
|
||||||
|
import triggerScheduleExecution from '@/api/server/schedules/triggerScheduleExecution';
|
||||||
|
import { ServerContext } from '@/state/server';
|
||||||
|
import useFlash from '@/plugins/useFlash';
|
||||||
|
import { Schedule } from '@/api/server/schedules/getServerSchedules';
|
||||||
|
|
||||||
|
const RunScheduleButton = ({ schedule }: { schedule: Schedule }) => {
|
||||||
|
const [ loading, setLoading ] = useState(false);
|
||||||
|
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||||
|
|
||||||
|
const id = ServerContext.useStoreState(state => state.server.data!.id);
|
||||||
|
const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule);
|
||||||
|
|
||||||
|
const onTriggerExecute = useCallback(() => {
|
||||||
|
clearFlashes('schedule');
|
||||||
|
setLoading(true);
|
||||||
|
triggerScheduleExecution(id, schedule.id)
|
||||||
|
.then(() => {
|
||||||
|
setLoading(false);
|
||||||
|
appendSchedule({ ...schedule, isProcessing: true });
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
clearAndAddHttpError({ error, key: 'schedules' });
|
||||||
|
})
|
||||||
|
.then(() => setLoading(false));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SpinnerOverlay visible={loading} size={'large'}/>
|
||||||
|
<Button
|
||||||
|
isSecondary
|
||||||
|
color={'grey'}
|
||||||
|
css={tw`flex-1 sm:flex-none border-transparent`}
|
||||||
|
disabled={schedule.isProcessing}
|
||||||
|
onClick={onTriggerExecute}
|
||||||
|
>
|
||||||
|
Run Now
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RunScheduleButton;
|
|
@ -4,7 +4,6 @@ import { Schedule } from '@/api/server/schedules/getServerSchedules';
|
||||||
import getServerSchedule from '@/api/server/schedules/getServerSchedule';
|
import getServerSchedule from '@/api/server/schedules/getServerSchedule';
|
||||||
import Spinner from '@/components/elements/Spinner';
|
import Spinner from '@/components/elements/Spinner';
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
import { httpErrorToHuman } from '@/api/http';
|
|
||||||
import EditScheduleModal from '@/components/server/schedules/EditScheduleModal';
|
import EditScheduleModal from '@/components/server/schedules/EditScheduleModal';
|
||||||
import NewTaskButton from '@/components/server/schedules/NewTaskButton';
|
import NewTaskButton from '@/components/server/schedules/NewTaskButton';
|
||||||
import DeleteScheduleButton from '@/components/server/schedules/DeleteScheduleButton';
|
import DeleteScheduleButton from '@/components/server/schedules/DeleteScheduleButton';
|
||||||
|
@ -18,6 +17,7 @@ import ScheduleTaskRow from '@/components/server/schedules/ScheduleTaskRow';
|
||||||
import isEqual from 'react-fast-compare';
|
import isEqual from 'react-fast-compare';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import ScheduleCronRow from '@/components/server/schedules/ScheduleCronRow';
|
import ScheduleCronRow from '@/components/server/schedules/ScheduleCronRow';
|
||||||
|
import RunScheduleButton from '@/components/server/schedules/RunScheduleButton';
|
||||||
|
|
||||||
interface Params {
|
interface Params {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -49,7 +49,7 @@ export default ({ match, history, location: { state } }: RouteComponentProps<Par
|
||||||
const id = ServerContext.useStoreState(state => state.server.data!.id);
|
const id = ServerContext.useStoreState(state => state.server.data!.id);
|
||||||
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
|
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
|
||||||
|
|
||||||
const { clearFlashes, addError } = useFlash();
|
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||||
const [ isLoading, setIsLoading ] = useState(true);
|
const [ isLoading, setIsLoading ] = useState(true);
|
||||||
const [ showEditModal, setShowEditModal ] = useState(false);
|
const [ showEditModal, setShowEditModal ] = useState(false);
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ export default ({ match, history, location: { state } }: RouteComponentProps<Par
|
||||||
.then(schedule => appendSchedule(schedule))
|
.then(schedule => appendSchedule(schedule))
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
addError({ message: httpErrorToHuman(error), key: 'schedules' });
|
clearAndAddHttpError({ error, key: 'schedules' });
|
||||||
})
|
})
|
||||||
.then(() => setIsLoading(false));
|
.then(() => setIsLoading(false));
|
||||||
}, [ match ]);
|
}, [ match ]);
|
||||||
|
@ -146,6 +146,11 @@ export default ({ match, history, location: { state } }: RouteComponentProps<Par
|
||||||
onDeleted={() => history.push(`/server/${id}/schedules`)}
|
onDeleted={() => history.push(`/server/${id}/schedules`)}
|
||||||
/>
|
/>
|
||||||
</Can>
|
</Can>
|
||||||
|
{schedule.isActive &&
|
||||||
|
<Can action={'schedule.update'}>
|
||||||
|
<RunScheduleButton schedule={schedule}/>
|
||||||
|
</Can>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,7 @@ Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServ
|
||||||
Route::post('/', 'Servers\ScheduleController@store');
|
Route::post('/', 'Servers\ScheduleController@store');
|
||||||
Route::get('/{schedule}', 'Servers\ScheduleController@view');
|
Route::get('/{schedule}', 'Servers\ScheduleController@view');
|
||||||
Route::post('/{schedule}', 'Servers\ScheduleController@update');
|
Route::post('/{schedule}', 'Servers\ScheduleController@update');
|
||||||
|
Route::post('/{schedule}/execute', 'Servers\ScheduleController@execute');
|
||||||
Route::delete('/{schedule}', 'Servers\ScheduleController@delete');
|
Route::delete('/{schedule}', 'Servers\ScheduleController@delete');
|
||||||
|
|
||||||
Route::post('/{schedule}/tasks', 'Servers\ScheduleTaskController@store');
|
Route::post('/{schedule}/tasks', 'Servers\ScheduleTaskController@store');
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Unit\Services\Schedules;
|
|
||||||
|
|
||||||
use Mockery as m;
|
|
||||||
use Tests\TestCase;
|
|
||||||
use Cron\CronExpression;
|
|
||||||
use Pterodactyl\Models\Task;
|
|
||||||
use Pterodactyl\Models\Schedule;
|
|
||||||
use Illuminate\Contracts\Bus\Dispatcher;
|
|
||||||
use Pterodactyl\Jobs\Schedule\RunTaskJob;
|
|
||||||
use Pterodactyl\Services\Schedules\ProcessScheduleService;
|
|
||||||
use Pterodactyl\Contracts\Repository\TaskRepositoryInterface;
|
|
||||||
use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface;
|
|
||||||
|
|
||||||
class ProcessScheduleServiceTest extends TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var \Illuminate\Contracts\Bus\Dispatcher|\Mockery\Mock
|
|
||||||
*/
|
|
||||||
private $dispatcher;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface|\Mockery\Mock
|
|
||||||
*/
|
|
||||||
private $scheduleRepository;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \Pterodactyl\Contracts\Repository\TaskRepositoryInterface|\Mockery\Mock
|
|
||||||
*/
|
|
||||||
private $taskRepository;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setup tests.
|
|
||||||
*/
|
|
||||||
public function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
$this->dispatcher = m::mock(Dispatcher::class);
|
|
||||||
$this->scheduleRepository = m::mock(ScheduleRepositoryInterface::class);
|
|
||||||
$this->taskRepository = m::mock(TaskRepositoryInterface::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that a schedule can be updated and first task set to run.
|
|
||||||
*/
|
|
||||||
public function testScheduleIsUpdatedAndRun()
|
|
||||||
{
|
|
||||||
$model = factory(Schedule::class)->make(['id' => 123]);
|
|
||||||
$model->setRelation('tasks', collect([$task = factory(Task::class)->make([
|
|
||||||
'sequence_id' => 1,
|
|
||||||
])]));
|
|
||||||
|
|
||||||
$this->scheduleRepository->shouldReceive('loadTasks')->with($model)->once()->andReturn($model);
|
|
||||||
|
|
||||||
$formatted = sprintf('%s %s %s * %s', $model->cron_minute, $model->cron_hour, $model->cron_day_of_month, $model->cron_day_of_week);
|
|
||||||
$this->scheduleRepository->shouldReceive('update')->with($model->id, [
|
|
||||||
'is_processing' => true,
|
|
||||||
'next_run_at' => CronExpression::factory($formatted)->getNextRunDate(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->taskRepository->shouldReceive('update')->with($task->id, ['is_queued' => true])->once();
|
|
||||||
|
|
||||||
$this->dispatcher->shouldReceive('dispatch')->with(m::on(function ($class) use ($model, $task) {
|
|
||||||
$this->assertInstanceOf(RunTaskJob::class, $class);
|
|
||||||
$this->assertSame($task->time_offset, $class->delay);
|
|
||||||
$this->assertSame($task->id, $class->task->id);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}))->once();
|
|
||||||
|
|
||||||
$this->getService()->handle($model);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance of the service for testing purposes.
|
|
||||||
*
|
|
||||||
* @return \Pterodactyl\Services\Schedules\ProcessScheduleService
|
|
||||||
*/
|
|
||||||
private function getService(): ProcessScheduleService
|
|
||||||
{
|
|
||||||
return new ProcessScheduleService($this->dispatcher, $this->scheduleRepository, $this->taskRepository);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue