Skip to content

Commit 5b8c5a1

Browse files
committed
Merge branch 'develop' into bugfix/FOUR-24108-A
2 parents 3732878 + 4da5af9 commit 5b8c5a1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1757
-857
lines changed

ProcessMaker/Console/Commands/BuildScriptExecutors.php

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ class BuildScriptExecutors extends Command
1818
*
1919
* @var string
2020
*/
21-
protected $signature = 'processmaker:build-script-executor {lang} {user?} {--rebuild}';
21+
protected $signature = 'processmaker:build-script-executor
22+
{lang : The ID or language of the script executor}
23+
{user? : The user ID to send the broadcast event to}
24+
{--rebuild : Rebuild the docker image}
25+
{--build-args= : The build arguments for the docker build command}';
2226

2327
/**
2428
* The console command description.
@@ -159,6 +163,12 @@ public function buildExecutor()
159163
$command = Docker::command() .
160164
" build --build-arg SDK_DIR=./sdk -t {$image} -f {$packagePath}/Dockerfile.custom {$packagePath}";
161165

166+
$buildArgs = $this->getBuildArgs();
167+
168+
foreach ($buildArgs as $buildArg) {
169+
$command .= ' ' . $buildArg;
170+
}
171+
162172
$this->execCommand($command);
163173

164174
$isNayra = $scriptExecutor->language === Base::NAYRA_LANG;
@@ -167,6 +177,29 @@ public function buildExecutor()
167177
}
168178
}
169179

180+
/**
181+
* Get the build arguments for the docker build command.
182+
*
183+
* @return array
184+
* - '--build-arg <key>=<value>'
185+
*/
186+
public function getBuildArgs(): array
187+
{
188+
$args = $this->option('build-args');
189+
190+
if ($args) {
191+
$buildArgs = [];
192+
193+
foreach (explode(',', $args) as $arg) {
194+
$buildArgs[] = '--build-arg ' . $arg;
195+
}
196+
197+
return $buildArgs;
198+
}
199+
200+
return [];
201+
}
202+
170203
public function getDockerfileContent(ScriptExecutor $scriptExecutor): string
171204
{
172205
$lang = $scriptExecutor->language;

ProcessMaker/Helpers/DataTypeHelper.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ private static function isDate($value)
1010
{
1111
if (is_string($value)) {
1212
if (strlen($value) > 5) {
13+
if (!preg_match('/\d{4}-\d{2}-\d{2}/', $value)) {
14+
return false;
15+
}
1316
try {
1417
$parsed = Carbon::parse($value);
1518
if ($parsed->isMidnight()) {

ProcessMaker/Http/Controllers/Api/UserController.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,10 +255,13 @@ public function getUsersTaskCount(Request $request)
255255
$processRequestToken = ProcessRequestToken::findOrFail($request->input('assignable_for_task_id'));
256256
if (config('app.reassign_restrict_to_assignable_users')) {
257257
$include_ids = $processRequestToken->process->getAssignableUsersByAssignmentType($processRequestToken);
258-
}
259-
$assignmentRule = $processRequestToken->getAssignmentRule();
260-
if ($assignmentRule === 'rule_expression' && $request->has('form_data')) {
261-
$include_ids = $processRequestToken->getAssigneesFromExpression($request->input('form_data'));
258+
$assignmentRule = $processRequestToken->getAssignmentRule();
259+
if ($assignmentRule === 'rule_expression' && $request->has('form_data')) {
260+
$include_ids = $processRequestToken->getAssigneesFromExpression($request->input('form_data'));
261+
}
262+
if ($assignmentRule === 'process_variable' && $request->has('form_data')) {
263+
$include_ids = $processRequestToken->getUsersFromProcessVariable($request->input('form_data'));
264+
}
262265
}
263266
}
264267

ProcessMaker/Http/Controllers/TaskController.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ public function edit(ProcessRequestToken $task, string $preview = '')
140140
$element = $task->getDefinition(true);
141141
$screenFields = $screenVersion ? $screenVersion->screenFilteredFields() : [];
142142
$taskDraftsEnabled = TaskDraft::draftsEnabled();
143-
143+
$isSmartExtractTask = $task->element_name === 'Manual Document Review';
144144
// Remove screen parent to reduce the size of the response
145145
$screen = $task->screen;
146146
$screen['parent'] = null;
@@ -190,6 +190,25 @@ public function edit(ProcessRequestToken $task, string $preview = '')
190190
'datetime_format',
191191
]);
192192
$userConfiguration = (new UserConfigurationController())->index();
193+
$hitlEnabled = config('smart-extract.hitl_enabled', false) && $isSmartExtractTask;
194+
195+
// Build the iframe source
196+
$iframeSrc = null;
197+
if ($hitlEnabled) {
198+
$dashboardUrl = config('smart-extract.dashboard_url');
199+
$requestData = $task->processRequest->data ?? [];
200+
201+
$documentToken = $requestData['documentToken'] ?? null;
202+
$fileId = $requestData['fileId'] ?? null;
203+
204+
if ($documentToken && $fileId && !empty($dashboardUrl)) {
205+
$queryParams = http_build_query([
206+
'documentToken' => $documentToken,
207+
'fileId' => $fileId,
208+
]);
209+
$iframeSrc = $dashboardUrl . '?' . $queryParams;
210+
}
211+
}
193212

194213
return view('tasks.edit', [
195214
'task' => $task,
@@ -204,6 +223,8 @@ public function edit(ProcessRequestToken $task, string $preview = '')
204223
'screenFields' => $screenFields,
205224
'taskDraftsEnabled' => $taskDraftsEnabled,
206225
'userConfiguration' => $userConfiguration,
226+
'hitlEnabled' => $hitlEnabled,
227+
'iframeSrc' => $iframeSrc,
207228
]);
208229
}
209230
}

ProcessMaker/Http/Middleware/HideServerHeaders.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class HideServerHeaders
5555
/**
5656
* Handle an incoming request.
5757
*
58-
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
58+
* @param Closure(Request): (Response) $next
5959
*/
6060
public function handle(Request $request, Closure $next): Response
6161
{

ProcessMaker/Http/Middleware/IsManager.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class IsManager
1313
/**
1414
* Handle an incoming request.
1515
*
16-
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
16+
* @param Closure(Request): (Response) $next
1717
*/
1818
public function handle(Request $request, Closure $next): Response
1919
{

ProcessMaker/Http/Resources/Task.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ public function toArray($request)
7474

7575
$this->addAssignableUsers($array, $include);
7676

77+
$this->mergeHitlCaseNumber($array);
78+
7779
return $array;
7880
}
7981

@@ -114,6 +116,28 @@ private function addAssignableUsers(&$array, $include)
114116
}
115117
}
116118

119+
private function mergeHitlCaseNumber(array &$array): void
120+
{
121+
if (!config('smart-extract.hitl_enabled')) {
122+
return;
123+
}
124+
125+
if (!empty(data_get($array, 'process_request.case_number'))) {
126+
return;
127+
}
128+
129+
$this->processRequest->loadMissing('parentRequest');
130+
$parentCaseNumber = $this->processRequest->parentRequest?->case_number;
131+
if (!$parentCaseNumber) {
132+
return;
133+
}
134+
135+
data_set($array, 'process_request.case_number', $parentCaseNumber);
136+
if (empty($array['case_number'])) {
137+
$array['case_number'] = $parentCaseNumber;
138+
}
139+
}
140+
117141
/**
118142
* Add the active users to the list of assigned users
119143
*
@@ -131,7 +155,7 @@ private function addActiveAssignedUsers(array $users, array $assignedUsers)
131155
->whereNotIn('status', Process::NOT_ASSIGNABLE_USER_STATUS)
132156
->whereIn('id', $chunk)
133157
->pluck('id')->toArray();
134-
$assignedUsers = array_merge($assignedUsers,$activeUsers);
158+
$assignedUsers = array_merge($assignedUsers, $activeUsers);
135159
}
136160

137161
return $assignedUsers;

ProcessMaker/Jobs/ErrorHandling.php

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,59 @@ public function setDefaultsFromDataSourceConfig(array $config)
211211
public static function convertResponseToException($result)
212212
{
213213
if ($result['status'] === 'error') {
214-
if (str_starts_with($result['message'], 'Command exceeded timeout of')) {
215-
throw new ScriptTimeoutException($result['message']);
214+
$rawMessage = $result['message'] ?? '';
215+
if (str_starts_with((string) $rawMessage, 'Command exceeded timeout of')) {
216+
throw new ScriptTimeoutException((string) $rawMessage);
217+
}
218+
219+
$message = self::extractScriptErrorMessage($result);
220+
221+
if (empty($message)) {
222+
$message = $rawMessage ?: 'Script execution failed with unknown error';
223+
}
224+
225+
throw new ScriptException($message);
226+
}
227+
}
228+
229+
/**
230+
* Extract a concise error message from the microservice response.
231+
*/
232+
private static function extractScriptErrorMessage(array $result): string
233+
{
234+
$candidates = [
235+
$result['output']['error'] ?? null,
236+
$result['output']['exception'] ?? null,
237+
$result['output']['stderr'] ?? null,
238+
$result['output']['stdout'] ?? null,
239+
$result['message'] ?? null,
240+
];
241+
242+
foreach ($candidates as $candidate) {
243+
if (is_string($candidate) || is_numeric($candidate)) {
244+
$short = self::shortenMessage((string) $candidate);
245+
if (!empty($short)) {
246+
return $short;
247+
}
216248
}
217-
throw new ScriptException(json_encode($result, JSON_PRETTY_PRINT));
218249
}
250+
251+
return '';
252+
}
253+
254+
/**
255+
* Keep only the first line of the error and limit its length to avoid noisy traces.
256+
*/
257+
private static function shortenMessage(string $message): string
258+
{
259+
$firstLine = strtok($message, "\n");
260+
$firstLine = $firstLine === false ? $message : $firstLine;
261+
$trimmed = trim($firstLine);
262+
263+
if (strlen($trimmed) > 400) {
264+
return substr($trimmed, 0, 400) . '';
265+
}
266+
267+
return $trimmed;
219268
}
220269
}

ProcessMaker/Jobs/RefreshArtisanCaches.php

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,30 +23,7 @@ class RefreshArtisanCaches implements ShouldQueue
2323
*/
2424
public function handle()
2525
{
26-
// Skip in testing environment because this reconnects the database
27-
// meaning we loose transactions, and sets the console output verbosity
28-
// to quiet so we loose expectsOutput assertions.
29-
if (app()->environment('testing')) {
30-
return;
31-
}
32-
33-
$options = [
34-
'--no-interaction' => true,
35-
'--quiet' => true,
36-
];
37-
38-
if (app()->configurationIsCached()) {
39-
// Run in a separate process to avoid the tenant being set.
40-
// We do not use a tenant-specific config cache file.
41-
Process::path(base_path())
42-
->env(['TENANT' => false, 'APP_URL' => false])
43-
->run(Application::formatCommandString('config:cache'))->throw();
44-
} else {
45-
Artisan::call('queue:restart', $options);
46-
47-
// We call this manually here since this job is dispatched
48-
// automatically when the config *is* cached
49-
RestartMessageConsumers::dispatchSync();
50-
}
26+
// Do not rebuild the cache and restart the queue any more.
27+
// This is no longer needed.
5128
}
5229
}

ProcessMaker/Models/CallActivity.php

Lines changed: 60 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -97,22 +97,10 @@ protected function completeSubprocess(TokenInterface $token, ExecutionInstanceIn
9797
$store = $closedInstance->getDataStore();
9898
$allData = $store->getData();
9999

100-
// Determine which data should be merged back from the subprocess.
101-
$updatedKeys = method_exists($store, 'getUpdated')
102-
? $store->getUpdated()
103-
: null;
104-
105-
if ($updatedKeys === null) {
106-
// Legacy behavior or no tracking available: copy all data.
107-
$data = $allData;
108-
} elseif ($updatedKeys === []) {
109-
// Nothing was updated in the subprocess: do not merge anything.
110-
$data = [];
111-
} else {
112-
// Merge only the explicitly updated keys.
113-
$updatedKeys = array_values((array) $updatedKeys);
114-
$data = array_intersect_key($allData, array_flip($updatedKeys));
115-
}
100+
$data = $this->resolveUpdatedData($store, $allData);
101+
$parentData = $token->getInstance()->getDataStore()->getData();
102+
$data = $this->mergeNewKeys($data, $allData, $parentData);
103+
$data = $this->mergeChangedKeys($data, $allData, $parentData);
116104

117105
$dataManager = new DataManager();
118106
$dataManager->updateData($token, $data);
@@ -125,6 +113,62 @@ protected function completeSubprocess(TokenInterface $token, ExecutionInstanceIn
125113
return $this;
126114
}
127115

116+
/**
117+
* Decide which data from the subprocess should be merged based on updated keys.
118+
*/
119+
protected function resolveUpdatedData($store, array $allData): array
120+
{
121+
$updatedKeys = method_exists($store, 'getUpdated')
122+
? $store->getUpdated()
123+
: null;
124+
125+
if ($updatedKeys === null) {
126+
return $allData;
127+
}
128+
129+
if ($updatedKeys === []) {
130+
return [];
131+
}
132+
133+
$updatedKeys = array_values((array) $updatedKeys);
134+
135+
return array_intersect_key($allData, array_flip($updatedKeys));
136+
}
137+
138+
/**
139+
* Merge keys that exist only in the subprocess data.
140+
*/
141+
protected function mergeNewKeys(array $data, array $allData, array $parentData): array
142+
{
143+
$newKeys = array_diff(array_keys($allData), array_keys($parentData));
144+
if (empty($newKeys)) {
145+
return $data;
146+
}
147+
148+
return $data + array_intersect_key($allData, array_flip($newKeys));
149+
}
150+
151+
/**
152+
* Merge keys that changed in the subprocess but may not have been tracked.
153+
*/
154+
protected function mergeChangedKeys(array $data, array $allData, array $parentData): array
155+
{
156+
$changedKeys = [];
157+
foreach ($allData as $key => $value) {
158+
if (array_key_exists($key, $parentData) && $parentData[$key] !== $value) {
159+
$changedKeys[] = $key;
160+
}
161+
}
162+
163+
if (empty($changedKeys)) {
164+
return $data;
165+
}
166+
167+
$pendingKeys = array_diff($changedKeys, array_keys($data));
168+
169+
return $data + array_intersect_key($allData, array_flip($pendingKeys));
170+
}
171+
128172
/**
129173
* Catch a subprocess error
130174
*

0 commit comments

Comments
 (0)