From 308054da436394a3c7979222f0e8350e203ce701 Mon Sep 17 00:00:00 2001 From: Eleazar Resendez Date: Fri, 10 Jul 2026 11:52:03 -0600 Subject: [PATCH 1/7] Add optional importingUserId to Options class and corresponding test --- ProcessMaker/ImportExport/Options.php | 5 ++++- ProcessMaker/Models/Bundle.php | 2 +- tests/Feature/ImportExport/OptionsTest.php | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 tests/Feature/ImportExport/OptionsTest.php diff --git a/ProcessMaker/ImportExport/Options.php b/ProcessMaker/ImportExport/Options.php index f70dcce89c..084489c4fc 100644 --- a/ProcessMaker/ImportExport/Options.php +++ b/ProcessMaker/ImportExport/Options.php @@ -34,9 +34,12 @@ class Options public $options; - public function __construct(array $options) + public ?int $importingUserId; + + public function __construct(array $options, ?int $importingUserId = null) { $this->options = $options; + $this->importingUserId = $importingUserId; } public function get($name, $uuid) diff --git a/ProcessMaker/Models/Bundle.php b/ProcessMaker/Models/Bundle.php index a62545e499..221faebc6a 100644 --- a/ProcessMaker/Models/Bundle.php +++ b/ProcessMaker/Models/Bundle.php @@ -390,7 +390,7 @@ public function installSettingsPayloads(array $payloads, $mode, $logger = null) { $options = new Options([ 'mode' => $mode, - ]); + ], $logger?->userId); $assets = []; foreach ($payloads as $payload) { diff --git a/tests/Feature/ImportExport/OptionsTest.php b/tests/Feature/ImportExport/OptionsTest.php new file mode 100644 index 0000000000..e5615ec4d8 --- /dev/null +++ b/tests/Feature/ImportExport/OptionsTest.php @@ -0,0 +1,15 @@ +assertNull((new Options([]))->importingUserId); + $this->assertSame(123, (new Options([], 123))->importingUserId); + } +} From c5b26203029c1e38edf5794e2d64acd8f0fd849a Mon Sep 17 00:00:00 2001 From: Eleazar Resendez Date: Fri, 10 Jul 2026 11:56:56 -0600 Subject: [PATCH 2/7] Pass importingUserId to Options in syncAssets and add corresponding test --- ProcessMaker/Models/Bundle.php | 2 +- tests/Model/BundleTest.php | 42 ++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/ProcessMaker/Models/Bundle.php b/ProcessMaker/Models/Bundle.php index 221faebc6a..bc89b7e0e7 100644 --- a/ProcessMaker/Models/Bundle.php +++ b/ProcessMaker/Models/Bundle.php @@ -463,7 +463,7 @@ public function install(array $payloads, $mode, $logger = null, $reinstall = fal $options = new Options([ 'mode' => $mode, - ]); + ], $logger->userId); $assets = []; foreach ($payloads as $payload) { $assets[] = DevLink::import($payload, $options, $logger); diff --git a/tests/Model/BundleTest.php b/tests/Model/BundleTest.php index cd0b31ff3f..c308476d92 100644 --- a/tests/Model/BundleTest.php +++ b/tests/Model/BundleTest.php @@ -2,10 +2,13 @@ namespace Tests\Model; +use ProcessMaker\ImportExport\Exporters\ScreenExporter; +use ProcessMaker\ImportExport\Logger; use ProcessMaker\Models\Bundle; use ProcessMaker\Models\BundleAsset; use ProcessMaker\Models\Process; use ProcessMaker\Models\Screen; +use ProcessMaker\Models\User; use Tests\Feature\ImportExport\HelperTrait; use Tests\TestCase; @@ -85,4 +88,43 @@ public function testReinstallBundle() $bundle->reinstall('update'); $this->assertEquals('Original Screen Name', $screen->refresh()->title); } + + public function testReinstallPassesImportingUserIdToExporter() + { + $screen = Screen::factory()->create(); + $screenUuid = $screen->uuid; + $remoteBundle = Bundle::factory()->create(); + $remoteBundle->syncAssets([$screen]); + $payloads = $remoteBundle->export(); + $payloads[0]['export'][$screenUuid]['exporter'] = ReinstallContextScreenExporter::class; + + $remoteBundle->delete(); + $screen->delete(); + + $localBundle = Bundle::factory()->create(); + $localBundle->install($payloads, 'update'); + $localBundle->savePayloadsToFile($payloads, [0 => []], null); + + $importingUser = User::factory()->create(); + ReinstallContextScreenExporter::$receivedImportingUserId = null; + + $localBundle->reinstall('update', new Logger($importingUser->id)); + + $this->assertSame( + $importingUser->id, + ReinstallContextScreenExporter::$receivedImportingUserId + ); + } +} + +class ReinstallContextScreenExporter extends ScreenExporter +{ + public static ?int $receivedImportingUserId = null; + + public function import(): bool + { + self::$receivedImportingUserId = $this->options->importingUserId; + + return parent::import(); + } } From b4544b830482829ff38e1d5c5500c4c829cb3965 Mon Sep 17 00:00:00 2001 From: Eleazar Resendez Date: Mon, 13 Jul 2026 11:38:18 -0600 Subject: [PATCH 3/7] FOUR-32196 Add granular dashboard and menu bundle selection --- .../Controllers/Api/DevLinkController.php | 119 ++++----- .../components/BundleConfigurations.vue | 47 ++-- .../admin/devlink/components/BundleDetail.vue | 71 ++++-- .../components/BundleSettingsModal.vue | 236 ++++++++++++------ .../components/platformConfigurations.js | 4 +- tests/Feature/Api/DevLinkTest.php | 83 ++++++ 6 files changed, 370 insertions(+), 190 deletions(-) diff --git a/ProcessMaker/Http/Controllers/Api/DevLinkController.php b/ProcessMaker/Http/Controllers/Api/DevLinkController.php index 8994184d1a..c2a6eea10b 100644 --- a/ProcessMaker/Http/Controllers/Api/DevLinkController.php +++ b/ProcessMaker/Http/Controllers/Api/DevLinkController.php @@ -4,14 +4,12 @@ use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\Request; -use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Notification; use Illuminate\Validation\Rule; use ProcessMaker\Events\CustomizeUiUpdated; use ProcessMaker\Exception\ValidationException; use ProcessMaker\Http\Controllers\Controller; use ProcessMaker\Http\Resources\ApiCollection; -use ProcessMaker\Jobs\CompileSass; use ProcessMaker\Jobs\CompileUI; use ProcessMaker\Jobs\DevLinkInstall; use ProcessMaker\Models\Bundle; @@ -23,6 +21,8 @@ use ProcessMaker\Models\SettingsMenus; use ProcessMaker\Models\User; use ProcessMaker\Notifications\BundleUpdatedNotification; +use ProcessMaker\Package\PackageDynamicUI\Models\Dashboard; +use ProcessMaker\Package\PackageDynamicUI\Models\Menu; class DevLinkController extends Controller { @@ -180,7 +180,7 @@ public function increaseBundleVersion(Bundle $bundle) return $bundle; } - public function bundleUpdated($bundleId, $token) + public function bundleUpdated(int $bundleId, string $token) { try { $bundle = Bundle::where('remote_id', $bundleId)->firstOrFail(); @@ -208,7 +208,7 @@ public function deleteBundle(Bundle $bundle) $bundle->delete(); } - public function installRemoteBundle(Request $request, DevLink $devLink, $remoteBundleId) + public function installRemoteBundle(Request $request, DevLink $devLink, int $remoteBundleId) { $updateType = $request->input('updateType', DevLinkInstall::MODE_UPDATE); DevLinkInstall::dispatch( @@ -342,7 +342,7 @@ public function addSharedAsset(Request $request) $sharedAsset->saveOrFail(); } - public function removeSharedAsset($id) + public function removeSharedAsset(int $id) { $deleted = Setting::destroy($id); @@ -371,7 +371,7 @@ public function installRemoteAsset(Request $request, DevLink $devLink) ]; } - public function remoteBundleVersion(DevLink $devLink, $remoteBundleId) + public function remoteBundleVersion(DevLink $devLink, int $remoteBundleId) { return $devLink->remoteBundle($remoteBundleId); } @@ -390,80 +390,63 @@ public function deleteBundleSetting(BundleSetting $bundleSetting) return response()->json(['message' => 'Bundle setting deleted.'], 200); } - public function getBundleSetting(Bundle $bundle, $settingKey) + public function getBundleSetting(Bundle $bundle, string $settingKey) { - $setting = $bundle->settings()->where('setting', $settingKey)->first(); + return $bundle->settings()->where('setting', $settingKey)->first(); + } - return $setting; + public function getBundleAllSettings(string $settingKey) + { + return match ($settingKey) { + 'ui_dashboards' => $this->getAvailableDashboards(), + 'ui_menus' => $this->getAvailableMenus(), + 'ui_settings' => Setting::whereIn('key', ['css-override', 'login-footer', 'logo-alt-text'])->get(), + default => Setting::where([ + ['group_id', SettingsMenus::getId($settingKey)], + ['hidden', 0], + ])->get(), + }; } - public function getBundleAllSettings($settingKey) + private function getAvailableDashboards() { - if ($settingKey === 'ui_settings') { - return Setting::whereIn('key', ['css-override', 'login-footer', 'logo-alt-text']) - ->get(); + if (!class_exists(Dashboard::class)) { + return []; } - return Setting::where([ - ['group_id', SettingsMenus::getId($settingKey)], - ['hidden', 0], - ])->get(); + return Dashboard::query() + ->orderBy('title') + ->get(['id', 'title']) + ->map(function (Dashboard $dashboard) { + return [ + 'key' => $dashboard->id, + 'name' => $dashboard->title, + ]; + }) + ->values(); } - public function refreshUi() + private function getAvailableMenus() { - CompileUI::dispatch(auth()->user()?->id); - CustomizeUiUpdated::dispatch([], [], false); + if (!class_exists(Menu::class)) { + return []; + } + + return Menu::query() + ->orderBy('name') + ->get(['id', 'name']) + ->map(function (Menu $menu) { + return [ + 'key' => $menu->id, + 'name' => $menu->name, + ]; + }) + ->values(); } - private function writeColors($data) + public function refreshUi(Request $request) { - // Now generate the _colors.scss file - $contents = "// Changed theme colors\n"; - foreach ($data as $value) { - $contents .= $value->id . ': ' . $value->value . ";\n"; - } - File::put(app()->resourcePath('sass') . '/_colors.scss', $contents); - } - - /** - * Write variables font in file - * - * @param $sansSerif - * @param $serif - */ - private function writeFonts($sansSerif) - { - $sansSerif = $sansSerif ? $sansSerif : $this->sansSerifFontDefault(); - // Generate the _fonts.scss file - $contents = "// Changed theme fonts\n"; - $contents .= '$font-family-sans-serif: ' . $sansSerif['id'] . " !default;\n"; - File::put(app()->resourcePath('sass') . '/_fonts.scss', $contents); - } - - /** - * run jobs compile - */ - private function compileSass($userId) - { - // Compile the Sass files - $this->dispatch(new CompileSass([ - 'tag' => 'sidebar', - 'origin' => 'resources/sass/sidebar/sidebar.scss', - 'target' => 'public/css/sidebar.css', - 'user' => $userId, - ])); - $this->dispatch(new CompileSass([ - 'tag' => 'app', - 'origin' => 'resources/sass/app.scss', - 'target' => 'public/css/app.css', - 'user' => $userId, - ])); - $this->dispatch(new CompileSass([ - 'tag' => 'queues', - 'origin' => 'resources/sass/admin/queues.scss', - 'target' => 'public/css/admin/queues.css', - 'user' => $userId, - ])); + CompileUI::dispatch($request->user()?->id); + CustomizeUiUpdated::dispatch([], [], false); } } diff --git a/resources/js/admin/devlink/components/BundleConfigurations.vue b/resources/js/admin/devlink/components/BundleConfigurations.vue index 4d347a1cf5..6f4109687a 100644 --- a/resources/js/admin/devlink/components/BundleConfigurations.vue +++ b/resources/js/admin/devlink/components/BundleConfigurations.vue @@ -4,14 +4,22 @@ {{ $t(title) }}
-
+
-
{{ $t(config.name) }}
-
{{ status(config) }}
+
+ {{ $t(config.name) }} +
+
+ {{ status(config) }} +