diff --git a/ProcessMaker/Events/ImportLog.php b/ProcessMaker/Events/ImportLog.php
index 96cbfe6aec..9168795f9d 100644
--- a/ProcessMaker/Events/ImportLog.php
+++ b/ProcessMaker/Events/ImportLog.php
@@ -16,7 +16,8 @@ public function __construct(
public $userId,
public $type,
public $message,
- public $additionalParams = []
+ public $additionalParams = [],
+ public $operationId = null,
) {
}
diff --git a/ProcessMaker/Http/Controllers/Api/DevLinkController.php b/ProcessMaker/Http/Controllers/Api/DevLinkController.php
index 29825f4d38..5859abd4e6 100644
--- a/ProcessMaker/Http/Controllers/Api/DevLinkController.php
+++ b/ProcessMaker/Http/Controllers/Api/DevLinkController.php
@@ -7,6 +7,7 @@
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Notification;
+use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use ProcessMaker\Events\CustomizeUiUpdated;
use ProcessMaker\Exception\ValidationException;
@@ -225,6 +226,7 @@ public function deleteBundle(Bundle $bundle)
public function installRemoteBundle(Request $request, DevLink $devLink, int $remoteBundleId)
{
$updateType = $request->input('updateType', DevLinkInstall::MODE_UPDATE);
+ $operationId = $this->operationId($request);
DevLinkInstall::dispatch(
$request->user()->id,
$devLink->id,
@@ -232,6 +234,7 @@ public function installRemoteBundle(Request $request, DevLink $devLink, int $rem
$remoteBundleId,
$updateType,
DevLinkInstall::TYPE_INSTALL_BUNDLE,
+ $operationId,
);
return [
@@ -242,6 +245,7 @@ public function installRemoteBundle(Request $request, DevLink $devLink, int $rem
public function reinstallBundle(Request $request, Bundle $bundle)
{
$updateType = $request->input('updateType', DevLinkInstall::MODE_UPDATE);
+ $operationId = $this->operationId($request);
DevLinkInstall::dispatch(
$request->user()->id,
$bundle->dev_link_id,
@@ -249,6 +253,7 @@ public function reinstallBundle(Request $request, Bundle $bundle)
$bundle->id,
$updateType,
DevLinkInstall::TYPE_REINSTALL_BUNDLE,
+ $operationId,
);
return [
@@ -370,6 +375,7 @@ public function removeSharedAsset(int $id)
public function installRemoteAsset(Request $request, DevLink $devLink)
{
$updateType = $request->input('updateType', DevLinkInstall::MODE_UPDATE);
+ $operationId = $this->operationId($request);
DevLinkInstall::dispatch(
$request->user()->id,
@@ -377,7 +383,8 @@ public function installRemoteAsset(Request $request, DevLink $devLink)
$request->input('class'),
$request->input('id'),
$updateType,
- DevLinkInstall::TYPE_IMPORT_ASSET
+ DevLinkInstall::TYPE_IMPORT_ASSET,
+ $operationId,
);
return [
@@ -502,4 +509,13 @@ public function refreshUi(Request $request)
CompileUI::dispatch($request->user()?->id);
CustomizeUiUpdated::dispatch([], [], false);
}
+
+ private function operationId(Request $request): string
+ {
+ $validated = $request->validate([
+ 'operation_id' => ['nullable', 'string', 'max:100'],
+ ]);
+
+ return $validated['operation_id'] ?? (string) Str::uuid();
+ }
}
diff --git a/ProcessMaker/ImportExport/Logger.php b/ProcessMaker/ImportExport/Logger.php
index f77b7adea4..2764d65eda 100644
--- a/ProcessMaker/ImportExport/Logger.php
+++ b/ProcessMaker/ImportExport/Logger.php
@@ -14,16 +14,19 @@ class Logger
public $userId = null;
+ public $operationId = null;
+
private $warnings = [];
private int $totalSteps = 1;
private int $currentStep = 1;
- public function __construct($userId = null)
+ public function __construct($userId = null, $operationId = null)
{
$this->pid = getmypid();
$this->userId = $userId;
+ $this->operationId = $operationId;
if ($userId) {
Event::listen(MessageLogged::class, function (MessageLogged $e) {
@@ -71,7 +74,13 @@ private function dispatch($type, $message, $additionalParams = [])
return;
}
- ImportLog::dispatch($this->userId, $type, substr($message, 0, 1000), $additionalParams);
+ ImportLog::dispatch(
+ $this->userId,
+ $type,
+ substr($message, 0, 1000),
+ $additionalParams,
+ $this->operationId,
+ );
$this->logToFile($type, $message, $additionalParams);
}
diff --git a/ProcessMaker/Jobs/DevLinkInstall.php b/ProcessMaker/Jobs/DevLinkInstall.php
index c30519e201..772aefbc31 100644
--- a/ProcessMaker/Jobs/DevLinkInstall.php
+++ b/ProcessMaker/Jobs/DevLinkInstall.php
@@ -33,6 +33,14 @@ class DevLinkInstall implements ShouldQueue
public $maxExceptions = 1;
+ /**
+ * Correlates progress events with the DevLink operation that started them.
+ *
+ * This remains nullable so jobs queued before this property was introduced
+ * can still be processed after an application upgrade.
+ */
+ public $operationId = null;
+
public function __construct(
public int $userId,
public int $devLinkId,
@@ -40,7 +48,9 @@ public function __construct(
public int $id,
public string $importMode,
public string $type,
+ $operationId = null,
) {
+ $this->operationId = $operationId;
}
/**
@@ -51,7 +61,7 @@ public function handle(): void
//log
\Log::info('DevLinkInstall job started: ' . $this->devLinkId);
$devLink = DevLink::findOrFail($this->devLinkId);
- $logger = new Logger($this->userId);
+ $logger = new Logger($this->userId, $this->operationId);
$lock = Cache::lock(ImportV2::CACHE_LOCK_KEY, ImportV2::RELEASE_LOCK_AFTER);
@@ -82,7 +92,7 @@ public function handle(): void
public function failed(Throwable $exception): void
{
- (new Logger($this->userId))->exception($exception);
+ (new Logger($this->userId, $this->operationId))->exception($exception);
// Unlock the job
// We can't use $this->lock->release() here because this is run in a new instance
diff --git a/resources/js/admin/devlink/components/AssetListing.vue b/resources/js/admin/devlink/components/AssetListing.vue
index a5716181d3..5a878dcee7 100644
--- a/resources/js/admin/devlink/components/AssetListing.vue
+++ b/resources/js/admin/devlink/components/AssetListing.vue
@@ -7,6 +7,7 @@ import types from './assetTypes';
import moment from 'moment';
import Header from './Header.vue';
import InstallProgress from './InstallProgress.vue';
+import createOperationId from '../createOperationId';
const route = useRoute();
const vue = getCurrentInstance().proxy;
@@ -20,6 +21,7 @@ const showInstallModal = ref(false);
const showConfirmModal = ref(false);
const selectedAsset = ref(null);
const installMode = ref('update');
+const operationId = ref('');
const page = ref(1);
const perPage = ref(15);
@@ -89,12 +91,14 @@ const install = (asset) => {
const confirmInstall = () => {
if (selectedAsset.value) {
+ operationId.value = createOperationId();
showConfirmModal.value = false;
showInstallModal.value = true;
const params = {
class: typeConfig.class,
id: selectedAsset.value.id,
- updateType: installMode.value
+ updateType: installMode.value,
+ operation_id: operationId.value,
};
ProcessMaker.apiClient
.post(`/devlink/${route.params.id}/install-remote-asset`, params)
@@ -210,7 +214,11 @@ const handleFilterChange = () => {
-
+
diff --git a/resources/js/admin/devlink/components/InstallProgress.vue b/resources/js/admin/devlink/components/InstallProgress.vue
index e96c14f3ca..a9e6014632 100644
--- a/resources/js/admin/devlink/components/InstallProgress.vue
+++ b/resources/js/admin/devlink/components/InstallProgress.vue
@@ -42,6 +42,10 @@ import {
} from "vue";
const props = defineProps({
+ operationId: {
+ type: String,
+ required: true,
+ },
requestError: {
type: String,
default: "",
@@ -82,6 +86,10 @@ onMounted(() => {
window.Echo.private(`ProcessMaker.Models.User.${userId}`).listen(
'.ImportLog',
(response) => {
+ if (response.operationId !== props.operationId) {
+ return;
+ }
+
showSpinner.value = false;
if (response.type === 'progress') {
progress.value = response.message;
diff --git a/resources/js/admin/devlink/components/Instance.vue b/resources/js/admin/devlink/components/Instance.vue
index be6c70400c..17abdcfd00 100644
--- a/resources/js/admin/devlink/components/Instance.vue
+++ b/resources/js/admin/devlink/components/Instance.vue
@@ -4,6 +4,7 @@ import debounce from 'lodash/debounce';
import { useRouter, useRoute } from 'vue-router/composables';
import InstanceTabs from './InstanceTabs.vue';
import InstallProgress from './InstallProgress.vue';
+import createOperationId from '../createOperationId';
const vue = getCurrentInstance().proxy;
const router = useRouter();
@@ -16,6 +17,7 @@ const warnings = ref([]);
const showInstallModal = ref(false);
const confirmUpdateVersion = ref(null);
const selectedOption = ref('update');
+const operationId = ref('');
const bundleAttributes = {
id: null,
name: '',
@@ -88,9 +90,12 @@ const install = (bundle) => {
cancelTitle: vue.$t('Cancel')
}).then((confirm) => {
if (confirm) {
+ operationId.value = createOperationId();
showInstallModal.value = true;
ProcessMaker.apiClient
- .post(`/devlink/${route.params.id}/remote-bundles/${bundle.id}/install`)
+ .post(`/devlink/${route.params.id}/remote-bundles/${bundle.id}/install`, {
+ operation_id: operationId.value,
+ })
.then((response) => {
// Handle the response as needed
});
@@ -98,10 +103,12 @@ const install = (bundle) => {
});
};
const executeUpdate = (updateType) => {
+ operationId.value = createOperationId();
showInstallModal.value = true;
ProcessMaker.apiClient
.post(`/devlink/${route.params.id}/remote-bundles/${selected.value.id}/install`, {
updateType,
+ operation_id: operationId.value,
})
.then((response) => {
// Handle the response as needed
@@ -163,6 +170,9 @@ const executeUpdate = (updateType) => {
diff --git a/resources/js/admin/devlink/components/UpdateBundle.vue b/resources/js/admin/devlink/components/UpdateBundle.vue
index 5b62f1d6fc..f86c4d8ab2 100644
--- a/resources/js/admin/devlink/components/UpdateBundle.vue
+++ b/resources/js/admin/devlink/components/UpdateBundle.vue
@@ -1,6 +1,7 @@