Skip to content

Commit 0839b5d

Browse files
committed
Refactor to allow only ScriptTasks to be re-tried through the UI
1 parent 5a7d940 commit 0839b5d

5 files changed

Lines changed: 235 additions & 71 deletions

File tree

‎ProcessMaker/Console/Commands/UnblockRequest.php‎

Lines changed: 14 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use ProcessMaker\Models\ProcessRequestToken;
1212
use ProcessMaker\Nayra\Bpmn\Models\ScriptTask;
1313
use ProcessMaker\Nayra\Bpmn\Models\ServiceTask;
14+
use ProcessMaker\RetryProcessRequest;
1415

1516
class UnblockRequest extends Command
1617
{
@@ -41,66 +42,32 @@ public function __construct()
4142
/**
4243
* Execute the console command.
4344
*
44-
* @return void
45+
* @return int
4546
*/
46-
public function handle()
47+
public function handle(): int
4748
{
4849
$requestId = $this->option('request');
4950

50-
$this->info("Retrying request {$requestId}...");
51-
5251
if (blank($request = ProcessRequest::find($requestId))) {
53-
$this->error(__("Request with ID: \"{$requestId}\" does not exist."));
52+
$this->error(__("ProcessRequest::{$requestId} was not found."));
5453

55-
return;
54+
return 1;
5655
}
5756

58-
$processed = false;
59-
$definitions = $request->process->getDefinitions();
60-
61-
// unlock the request
62-
ProcessRequestLock::where('process_request_id', $requestId)->delete();
57+
$retryRequest = RetryProcessRequest::for($request);
6358

64-
foreach ($this->getOpenTasks($requestId) as $token) {
65-
$task = $definitions->getEvent($token->element_id);
66-
$instance = $token->processRequest;
67-
$process = $instance->process;
59+
if (!$retryRequest->hasRetriableTasks()) {
60+
$this->warn("ProcessRequest::{$requestId}: No retriable tasks found.");
6861

69-
switch (get_class($task)) {
70-
case ScriptTask::class:
71-
$processed = true;
72-
$this->info("Request {$requestId}: Running Script Task '{$token->element_id}' ({$token->element_name}) again...");
73-
Log::info("Retrying script task with ID {$token->element_id} ({$token->element_name})");
74-
RunScriptTask::dispatch($process, $instance, $token, []);
75-
break;
76-
77-
case ServiceTask::class:
78-
$processed = true;
79-
$this->info("Request {$requestId}: Running Service Task '{$token->element_id}' ({$token->element_name}) again...");
80-
Log::info("Retrying service task with ID {$token->element_id} ({$token->element_name})");
81-
RunServiceTask::dispatch($process, $instance, $token, []);
82-
break;
83-
}
62+
return 1;
8463
}
8564

86-
if (!$processed) {
87-
$this->info("Request {$requestId}: no pending scripts found.");
65+
$retryRequest->retry();
66+
67+
foreach ($retryRequest::$output as $line) {
68+
$this->info($line);
8869
}
89-
}
9070

91-
/**
92-
* Find all script and service tasks of a request that are halted for some reason
93-
*
94-
* @param $requestId
95-
*
96-
* @return mixed
97-
*/
98-
private function getOpenTasks($requestId)
99-
{
100-
return ProcessRequestToken::whereIn('status', ['FAILING', 'ACTIVE', 'ERROR'])
101-
->whereIn('element_type', ['scriptTask', 'serviceTask', 'task'])
102-
->where('process_request_id', $requestId)
103-
->get()
104-
->all();
71+
return 0;
10572
}
10673
}

‎ProcessMaker/Http/Controllers/Api/ProcessRequestController.php‎

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use ProcessMaker\Nayra\Contracts\Bpmn\CatchEventInterface;
3232
use ProcessMaker\Notifications\ProcessCanceledNotification;
3333
use ProcessMaker\Query\SyntaxError;
34+
use ProcessMaker\RetryProcessRequest;
3435
use Symfony\Component\HttpFoundation\IpUtils;
3536
use Throwable;
3637

@@ -233,6 +234,7 @@ public function show(ProcessRequest $request)
233234
*
234235
* @return \Illuminate\Http\JsonResponse
235236
* @throws \Illuminate\Auth\Access\AuthorizationException
237+
* @throws \Illuminate\Validation\ValidationException
236238
*/
237239
public function retry(ProcessRequest $request, Request $httpRequest): JsonResponse
238240
{
@@ -246,17 +248,26 @@ public function retry(ProcessRequest $request, Request $httpRequest): JsonRespon
246248
]);
247249
}
248250

249-
try {
250-
$exitCode = Artisan::call('processmaker:unblock-request', [
251-
'--request' => $request->id,
251+
$retryRequest = RetryProcessRequest::for($request);
252+
253+
if (!$retryRequest->hasRetriableTasks()) {
254+
return response()->json([
255+
'message' => ['No tasks available to retry'],
256+
'success' => false,
252257
]);
258+
}
253259

254-
$output = Artisan::output();
260+
try {
261+
$retryRequest->retry();
262+
263+
return response()->json([
264+
'message' => $retryRequest::$output,
265+
'success' => true,
266+
]);
255267
} catch (Throwable $throwable) {
256-
$exitCode = 1;
257-
$output = $throwable->getMessage();
268+
$message = $throwable->getMessage();
258269

259-
Log::error("UnblockRequest Failed for Request ID: {$request->id}", [
270+
Log::error("ProcessRequest::{$request->id} Retry Failed", [
260271
'message' => $throwable->getMessage(),
261272
'line' => $throwable->getLine(),
262273
'file' => $throwable->getFile(),
@@ -265,11 +276,9 @@ public function retry(ProcessRequest $request, Request $httpRequest): JsonRespon
265276
]);
266277
}
267278

268-
$success = $exitCode === 0 && !is_int(strpos($output, 'no pending scripts found'));
269-
270279
return response()->json([
271-
'message' => $output,
272-
'success' => $success,
280+
'message' => [$message],
281+
'success' => false,
273282
]);
274283
}
275284

‎ProcessMaker/Http/Controllers/RequestController.php‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use ProcessMaker\Models\Screen;
1717
use ProcessMaker\Models\ScreenVersion;
1818
use ProcessMaker\Package\PackageComments\PackageServiceProvider;
19+
use ProcessMaker\RetryProcessRequest;
1920
use ProcessMaker\Traits\HasControllerAddons;
2021
use ProcessMaker\Traits\SearchAutocompleteTrait;
2122
use Spatie\MediaLibrary\MediaCollections\Models\Media;
@@ -110,10 +111,11 @@ public function show(ProcessRequest $request, Media $mediaItems)
110111
$canCancel = Auth::user()->can('cancel', $request->processVersion);
111112
$canViewComments = (Auth::user()->hasPermissionsFor('comments')->count() > 0) || class_exists(PackageServiceProvider::class);
112113
$canManuallyComplete = Auth::user()->is_administrator && $request->status === 'ERROR';
114+
$canRetry = false;
113115

114-
// While redundant, it's to emphasize the two different
115-
// permissions for now for a refactor in the future
116-
$canRetry = $canManuallyComplete;
116+
if ($canManuallyComplete) {
117+
$canRetry = RetryProcessRequest::for($request)->hasRetriableTasks();
118+
}
117119

118120
$files = $request->getMedia();
119121

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
<?php
2+
3+
namespace ProcessMaker;
4+
5+
use Illuminate\Database\Eloquent\Collection;
6+
use Illuminate\Database\Eloquent\Relations\HasMany;
7+
use Illuminate\Support\Facades\Auth;
8+
use Illuminate\Support\Facades\Log;
9+
use ProcessMaker\Jobs\RunScriptTask;
10+
use ProcessMaker\Jobs\RunServiceTask;
11+
use ProcessMaker\Models\Comment;
12+
use ProcessMaker\Models\ProcessRequest;
13+
use ProcessMaker\Models\ProcessRequestLock;
14+
use ProcessMaker\Models\ProcessRequestToken;
15+
use ProcessMaker\Models\User;
16+
use ProcessMaker\Nayra\Bpmn\Models\ScriptTask;
17+
use ProcessMaker\Nayra\Bpmn\Models\ServiceTask;
18+
use ProcessMaker\Repositories\BpmnDocument;
19+
20+
class RetryProcessRequest
21+
{
22+
public static array $output = [];
23+
24+
private static array $taskTypes = [];
25+
26+
private ProcessRequest $processRequest;
27+
28+
private BpmnDocument $bpmnDefinitions;
29+
30+
public function __construct(ProcessRequest $processRequest)
31+
{
32+
$this->processRequest = $processRequest;
33+
34+
$this->bpmnDefinitions = $this->getBpmnDefinitions();
35+
36+
$this->determineTaskTypes();
37+
}
38+
39+
public static function for(ProcessRequest $processRequest): self
40+
{
41+
return new self($processRequest);
42+
}
43+
44+
public function hasRetriableTasks(): bool
45+
{
46+
return $this->retriableTasksQuery()->exists();
47+
}
48+
49+
public function retry(): void
50+
{
51+
if (!$this->hasRetriableTasks()) {
52+
return;
53+
}
54+
55+
$this->unlockProcessRequest();
56+
57+
$this->getRetriableTasks()->each(function (ProcessRequestToken $token) {
58+
// Get the task to retry from the BPMN definitions
59+
$task = $this->bpmnDefinitions->getEvent($element = $token->element_id);
60+
61+
// $job will remain false if we can't find the correct
62+
// job type to dispatch in order to retry the task
63+
$job = false;
64+
65+
// Find the correct job type to retry the task
66+
switch ($task::class) {
67+
case ScriptTask::class:
68+
$job = RunScriptTask::class;
69+
break;
70+
71+
case ServiceTask::class:
72+
$job = RunServiceTask::class;
73+
break;
74+
}
75+
76+
if (!$job) {
77+
return;
78+
}
79+
80+
$output = 'Retrying ' . class_basename($task) . " ({$element}) for Request::{$token->processRequest->id}";
81+
82+
static::$output[] = $output;
83+
84+
Log::info($output, [
85+
'user_initializing_retry' => $this->initializingUser(),
86+
'token' => $token,
87+
]);
88+
89+
$job::dispatch($token->processRequest->process, $token->processRequest, $token, []);
90+
});
91+
92+
$this->createRequestComment();
93+
}
94+
95+
protected function initializingUser(): User|bool
96+
{
97+
return Auth::check() ? Auth::user() : false;
98+
}
99+
100+
protected function createRequestComment(): void
101+
{
102+
$comment = (new Comment)->fill([
103+
'type' => 'LOG',
104+
'commentable_type' => ProcessRequest::class,
105+
'commentable_id' => $this->processRequest->id,
106+
'subject' => __('Request Retried'),
107+
]);
108+
109+
if ($user = $this->initializingUser()) {
110+
$comment->setAttribute('user_id', $user->id);
111+
$comment->setAttribute('body', $user->fullname . ' ' . __('retried the request from an error state'));
112+
} else {
113+
$comment->setAttribute('body', __('Request was retried from an error state'));
114+
}
115+
116+
$comment->save();
117+
}
118+
119+
private function determineTaskTypes(): void
120+
{
121+
if (app()->runningInConsole()) {
122+
static::$taskTypes = ['scriptTask', 'serviceTask', 'task'];
123+
} else {
124+
static::$taskTypes = ['scriptTask'];
125+
}
126+
}
127+
128+
protected function getRetriableTasks(): Collection
129+
{
130+
return $this->retriableTasksQuery()->get();
131+
}
132+
133+
protected function retriableTasksQuery(): HasMany
134+
{
135+
$tokensQuery = $this->processRequest->tokens();
136+
137+
$tokensQuery->whereIn('status', ['FAILING', 'ACTIVE', 'ERROR']);
138+
139+
$tokensQuery->whereIn('element_type', static::$taskTypes);
140+
141+
return $tokensQuery;
142+
}
143+
144+
protected function unlockProcessRequest(): void
145+
{
146+
ProcessRequestLock::where('process_request_id', $this->processRequest->id)
147+
->delete();
148+
}
149+
150+
protected function getBpmnDefinitions(): BpmnDocument
151+
{
152+
return $this->processRequest->process->getDefinitions();
153+
}
154+
}

0 commit comments

Comments
 (0)