diff --git a/ProcessMaker/Http/Controllers/Api/DevLinkController.php b/ProcessMaker/Http/Controllers/Api/DevLinkController.php index e77f926d3e..3eb474fee0 100644 --- a/ProcessMaker/Http/Controllers/Api/DevLinkController.php +++ b/ProcessMaker/Http/Controllers/Api/DevLinkController.php @@ -208,6 +208,15 @@ public function exportLocalBundleSettings(Bundle $bundle) return ['settings' => $bundle->exportSettings()]; } + public function exportLocalBundleSettingPayloads(Bundle $bundle) + { + if ($bundle->settings->isEmpty()) { + return ['payloads' => [0 => []]]; + } + + return ['payloads' => $bundle->exportSettingPayloads()]; + } + public function exportLocalAsset(Request $request) { $asset = $request->input('class')::findOrFail($request->input('id')); @@ -223,7 +232,7 @@ public function addAsset(Request $request, Bundle $bundle) public function addSettings(Request $request, Bundle $bundle) { - $bundle->addSettings($request->input('setting'), $request->input('config')); + $bundle->addSettings($request->input('setting'), $request->input('config'), $request->input('type')); } public function addAssetToBundles(Request $request) @@ -238,6 +247,17 @@ public function addAssetToBundles(Request $request) } } + public function addSettingToBundles(Request $request) + { + $bundles = $request->input('bundles'); + foreach ($bundles as $id) { + $bundle = Bundle::find($id); + if ($bundle) { + $bundle->addSettingToBundles($request->input('setting'), $request->input('config'), $request->input('type')); + } + } + } + public function sharedAssets(Request $request) { return Setting::Where('group', 'Devlink')->get(); diff --git a/ProcessMaker/ImportExport/Dependent.php b/ProcessMaker/ImportExport/Dependent.php index 9d25b3e412..a115882bb7 100644 --- a/ProcessMaker/ImportExport/Dependent.php +++ b/ProcessMaker/ImportExport/Dependent.php @@ -56,7 +56,7 @@ public function __get($property) 'attributes' => $this->fallbackMatches, ]; - list($_, $model) = Manifest::getModel($this->uuid, $assetInfo, 'discard', $this->exporterClass); + list($_, $model) = Manifest::getModel($this->uuid, $assetInfo, 'discard', $this->exporterClass, false); // Only return the model if it is persisted in the database if ($model && $model->exists) { diff --git a/ProcessMaker/ImportExport/Importer.php b/ProcessMaker/ImportExport/Importer.php index dd08295bb1..9b0af8d471 100644 --- a/ProcessMaker/ImportExport/Importer.php +++ b/ProcessMaker/ImportExport/Importer.php @@ -36,7 +36,7 @@ public function previewImport() public function loadManifest() { - return Manifest::fromArray($this->payload['export'], $this->options, $this->logger); + return Manifest::fromArray($this->payload['export'], $this->options, $this->logger, $this->payload['root']); } public function doImport($existingAssetInDatabase = null, $importingFromTemplate = false) @@ -50,6 +50,7 @@ public function doImport($existingAssetInDatabase = null, $importingFromTemplate $this->logger->log("Importing {$count} assets"); foreach ($this->manifest->all() as $uuid => $exporter) { $this->logger->setStatus('saving', $uuid); + \Log::info('Importing ' . get_class($exporter->model), ['mode' => $exporter->mode]); if ($exporter->mode !== 'discard') { $this->logger->log('Importing ' . get_class($exporter->model)); if ($exporter->disableEventsWhenImporting) { diff --git a/ProcessMaker/ImportExport/Manifest.php b/ProcessMaker/ImportExport/Manifest.php index 1b553cd592..494d3197b5 100644 --- a/ProcessMaker/ImportExport/Manifest.php +++ b/ProcessMaker/ImportExport/Manifest.php @@ -81,7 +81,7 @@ public function toArray($skipHidden = false) return $manifest; } - public static function fromArray(array $array, Options $options, $logger) + public static function fromArray(array $array, Options $options, $logger, $root) { self::$logger = $logger; @@ -95,7 +95,8 @@ public static function fromArray(array $array, Options $options, $logger) $exporterClass = $assetInfo['exporter']; $modeOption = $options->get('mode', $uuid); $saveAssetsModeOption = $options->get('saveAssetsMode', $uuid); - list($mode, $model, $matchedBy) = self::getModel($uuid, $assetInfo, $modeOption, $exporterClass); + $isRoot = $uuid === $root; + list($mode, $model, $matchedBy) = self::getModel($uuid, $assetInfo, $modeOption, $exporterClass, $isRoot); $model = self::updateBPMNDefinitions($model, $saveAssetsModeOption); $exporter = new $exporterClass($model, $manifest, $options, false); $exporter->logger = $logger; @@ -134,7 +135,7 @@ public function push(string $uuid, ExporterInterface $exporter) $this->manifest[$uuid] = $exporter; } - public static function getModel($uuid, $assetInfo, $mode, $exporterClass) + public static function getModel($uuid, $assetInfo, $mode, $exporterClass, $isRoot) { $model = null; $class = $assetInfo['model']; @@ -153,7 +154,7 @@ public static function getModel($uuid, $assetInfo, $mode, $exporterClass) } } - if ($exporterClass::doNotImport($uuid, $assetInfo)) { + if (!$isRoot && $exporterClass::doNotImport($uuid, $assetInfo)) { $mode = 'discard'; } diff --git a/ProcessMaker/Models/Bundle.php b/ProcessMaker/Models/Bundle.php index 31112a4a8b..2a6134f9b6 100644 --- a/ProcessMaker/Models/Bundle.php +++ b/ProcessMaker/Models/Bundle.php @@ -9,6 +9,8 @@ use ProcessMaker\ImportExport\Logger; use ProcessMaker\ImportExport\Options; use ProcessMaker\Models\ProcessMakerModel; +use ProcessMaker\Models\Setting; +use ProcessMaker\Models\SettingsMenus; use Spatie\MediaLibrary\HasMedia; use Spatie\MediaLibrary\InteractsWithMedia; @@ -78,6 +80,17 @@ public function export() } public function exportSettings() + { + $exports = []; + + foreach ($this->settings as $setting) { + $exports[] = $setting; + } + + return $exports; + } + + public function exportSettingPayloads() { return $this->settings()->get()->map(function ($setting) { return $setting->export(); @@ -138,18 +151,81 @@ public function addAsset(ProcessMakerModel $asset) ]); } - public function addSettings($setting, $config) + public function addSettings($setting, $newId, $type = null) { - $exists = $this->settings()->where('setting', $setting)->exists(); - if ($exists) { - $this->settings()->where('setting', $setting)->update([ - 'config' => $config, + $existingSetting = $this->settings()->where('setting', $setting)->first(); + //If $newId is null, set config to null + if (is_null($newId) && is_null($type)) { + if ($existingSetting) { + $existingSetting->update(['config' => null]); + } else { + BundleSetting::create([ + 'bundle_id' => $this->id, + 'setting' => $setting, + 'config' => null, + ]); + } + + return; + } + // verify if newId is a json with id key + $decodedNewId = json_decode($newId, true); + if (json_last_error() === JSON_ERROR_NONE) { + if (is_array($decodedNewId)) { + $newId = $decodedNewId; + } elseif (isset($decodedNewId['id'])) { + $newId = [$decodedNewId['id']]; + } else { + $newId = ['id' => [$decodedNewId]]; + } + } else { + $newId = ['id' => [$newId]]; + } + + if ($existingSetting) { + // Decode the existing JSON + $config = json_decode($existingSetting->config, true); + // Ensure 'id' is an array + if (!isset($config['id']) || !is_array($config['id'])) { + $config['id'] = []; + } + // Add the new ID + foreach ($newId['id'] as $id) { + $config['id'][] = $id; + } + // Remove duplicates + $config['id'] = array_unique($config['id']); + //reindex the array + $config['id'] = array_values($config['id']); + // Update the config + $existingSetting->update([ + 'config' => json_encode($config), ]); } else { + // Create a new BundleSetting with the initial ID + $config = ['id' => []]; + if ($newId && $type !== 'settings') { + foreach ($newId['id'] as $id) { + $config['id'][] = $id; + } + } + + if ($type === 'settings') { + $settingsMenu = SettingsMenus::where('menu_group', $setting)->first(); + $settingsKeys = Setting::where([ + ['group_id', '=', $settingsMenu->id], + ['hidden', '=', false], + ])->pluck('key')->toArray(); + if ($settingsMenu) { + $config['id'] = $settingsKeys; + $config['type'] = $type; + } + } + BundleSetting::create([ 'bundle_id' => $this->id, 'setting' => $setting, - 'config' => $config, + 'config' => json_encode($config), ]); } } @@ -166,6 +242,18 @@ public function addAssetToBundles(ProcessMakerModel $asset) return $message; } + public function addSettingToBundles($setting, $newId, $type = null) + { + $message = null; + try { + $this->addSettings($setting, $newId, $type); + } catch (ValidationException $ve) { + $message = $ve->getMessage(); + } + + return $message; + } + public function validateEditable() { if (!$this->editable()) { @@ -226,6 +314,55 @@ public function installSettings($settings) } } + public function installSettingsPayloads(array $payloads, $mode, $logger = null) + { + $options = new Options([ + 'mode' => $mode, + ]); + $clientRepository = app('Laravel\Passport\ClientRepository'); + + $assets = []; + foreach ($payloads as $payload) { + if (isset($payload[0]['export'])) { + $logger->status('Installing bundle settings on the this instance'); + $logger->setSteps($payloads[0]); + $assets[] = DevLink::import($payload[0], $options, $logger); + } else { + switch ($payload[0]['setting_type']) { + case 'auth_clients': + $clientRepository->create( + null, + $payload[0]['name'], + $payload[0]['redirect'], + $payload[0]['provider'], + $payload[0]['personal_access_client'], + $payload[0]['password_client'] + ); + break; + case 'User Settings': + case 'Email': + case 'Integrations': + case 'Log-In & Auth': + $settingsMenu = SettingsMenus::where('menu_group', $payload[0]['setting_type'])->first(); + Setting::updateOrCreate([ + 'key' => $payload[0]['key'], + ], [ + 'config' => $payload[0]['config'], + 'name' => $payload[0]['name'], + 'helper' => $payload[0]['helper'], + 'format' => $payload[0]['format'], + 'hidden' => $payload[0]['hidden'], + 'readonly' => $payload[0]['readonly'], + 'ui' => $payload[0]['ui'], + 'group_id' => $settingsMenu->id, + 'group' => $payload[0]['group'], + ]); + break; + } + } + } + } + public function install(array $payloads, $mode, $logger = null) { if ($logger === null) { diff --git a/ProcessMaker/Models/BundleSetting.php b/ProcessMaker/Models/BundleSetting.php index 35de7bf8dc..9a8afb14a4 100644 --- a/ProcessMaker/Models/BundleSetting.php +++ b/ProcessMaker/Models/BundleSetting.php @@ -3,7 +3,18 @@ namespace ProcessMaker\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; +use ProcessMaker\Enums\ExporterMap; +use ProcessMaker\ImportExport\Exporter; +use ProcessMaker\ImportExport\Exporters\GroupExporter; +use ProcessMaker\ImportExport\Exporters\ScriptExecutorExporter; +use ProcessMaker\ImportExport\Exporters\UserExporter; use ProcessMaker\Models\ProcessMakerModel; +use ProcessMaker\Package\PackageDynamicUI\ImportExport\DashboardExporter; +use ProcessMaker\Package\PackageDynamicUI\ImportExport\MenuExporter; +use ProcessMaker\Package\PackageDynamicUI\Models\Dashboard; +use ProcessMaker\Package\PackageDynamicUI\Models\Menu; +use ProcessMaker\Package\Translations\ImportExport\TranslatableExporter; +use ProcessMaker\Package\Translations\Models\Translatable; class BundleSetting extends ProcessMakerModel { @@ -26,9 +37,103 @@ public function bundle() public function export() { - return [ - 'setting' => $this->setting, - 'config' => $this->config, - ]; + $configData = json_decode($this->config, true); + $ids = $configData['id'] ?? []; + + switch ($this->setting) { + case 'users': + if (empty($this->config)) { + $users = User::all(); + } else { + $users = User::whereIn('id', $ids)->get(); + } + + return $users->map(function ($user) { + return $this->exportHelper($user, UserExporter::class); + }); + case 'groups': + if (empty($this->config)) { + $groups = Group::all(); + } else { + $groups = Group::whereIn('id', $ids)->get(); + } + + return $groups->map(function ($group) { + return $this->exportHelper($group, GroupExporter::class); + }); + case 'script_executors': + if (empty($this->config)) { + $scriptExecutors = ScriptExecutor::all(); + } else { + $scriptExecutors = ScriptExecutor::whereIn('id', $ids)->get(); + } + + return $scriptExecutors->map(function ($scriptExecutor) { + return $this->exportHelper($scriptExecutor, ScriptExecutorExporter::class); + }); + case 'ui_dashboards': + if (empty($this->config)) { + $uiDashboards = Dashboard::all(); + } else { + $uiDashboards = Dashboard::whereIn('id', $ids)->get(); + } + + return $uiDashboards->map(function ($uiDashboard) { + return $this->exportHelper($uiDashboard, DashboardExporter::class); + }); + case 'ui_menus': + if (empty($this->config)) { + $uiMenus = Menu::all(); + } else { + $uiMenus = Menu::whereIn('id', $ids)->get(); + } + + return $uiMenus->map(function ($uiMenu) { + return $this->exportHelper($uiMenu, MenuExporter::class); + }); + case 'translations': + if (empty($this->config)) { + $translations = Translatable::all(); + } else { + $translations = Translatable::whereIn('key', $ids)->get(); + } + + return $translations->map(function ($translation) { + return $this->exportHelper($translation, TranslatableExporter::class); + }); + case 'auth_clients': + if (empty($this->config)) { + $authClients = \Laravel\Passport\Client::where('revoked', false)->get(); + } else { + $authClients = \Laravel\Passport\Client::where('revoked', false)->whereIn('id', $ids)->get(); + } + + return $authClients->map(function ($authClient) { + $authClient->setting_type = $this->setting; + + return $authClient; + }); + case 'Log-In & Auth': + case 'User Settings': + case 'Email': + case 'Integrations': + $bundleSettings = Setting::whereIn('key', $ids)->get(); + + return $bundleSettings->map(function ($bundleSetting) { + $bundleSetting->setting_type = $this->setting; + + return $bundleSetting; + }); + } + + return []; + } + + public function exportHelper($model, $exporterClass, $options = null, $ignoreExplicitExport = true) + { + $exporter = new Exporter(false, $ignoreExplicitExport); + $exporter->export($model, $exporterClass, $options); + + return $exporter->payload(); } } diff --git a/ProcessMaker/Models/DevLink.php b/ProcessMaker/Models/DevLink.php index cf831d0210..9fd0724379 100644 --- a/ProcessMaker/Models/DevLink.php +++ b/ProcessMaker/Models/DevLink.php @@ -147,6 +147,10 @@ public function installRemoteBundle($remoteBundleId, $updateType) route('api.devlink.export-local-bundle-settings', ['bundle' => $remoteBundleId], false) )->json(); + $bundleSettingsPayloads = $this->client()->get( + route('api.devlink.export-local-bundle-setting-payloads', ['bundle' => $remoteBundleId], false) + )->json(); + $bundle = Bundle::updateOrCreate( [ 'remote_id' => $remoteBundleId, @@ -160,7 +164,9 @@ public function installRemoteBundle($remoteBundleId, $updateType) ); $bundle->install($bundleExport['payloads'], $updateType, $this->logger); + $bundle->installSettingsPayloads($bundleSettingsPayloads['payloads'], $updateType, $this->logger); $bundle->installSettings($bundleSettingsExport['settings']); + $this->logger->setStatus('done'); } diff --git a/ProcessMaker/Models/Setting.php b/ProcessMaker/Models/Setting.php index e6167a2517..9119959002 100644 --- a/ProcessMaker/Models/Setting.php +++ b/ProcessMaker/Models/Setting.php @@ -143,7 +143,7 @@ public static function messages() * * @param string $key * - * @return \ProcessMaker\Models\Setting|null + * @return Setting|null * @throws \Exception */ public static function byKey(string $key) @@ -382,7 +382,7 @@ public static function getFavicon() */ public static function groupsByMenu($menuId) { - $query = Setting::query() + $query = self::query() ->select('group') ->groupBy('group') ->where('group_id', $menuId) @@ -409,7 +409,7 @@ public static function groupsByMenu($menuId) */ public static function updateSettingsGroup($settingsGroup, $id) { - Setting::where('group', $settingsGroup)->whereNull('group_id')->chunk( + self::where('group', $settingsGroup)->whereNull('group_id')->chunk( 50, function ($settings) use ($id) { foreach ($settings as $setting) { @@ -429,7 +429,7 @@ function ($settings) use ($id) { */ public static function updateAllSettingsGroupId() { - Setting::whereNull('group_id')->chunk(100, function ($settings) { + self::whereNull('group_id')->chunk(100, function ($settings) { $defaultId = SettingsMenus::EMAIL_MENU_GROUP; foreach ($settings as $setting) { // Define the value of 'menu_group' based on 'group' @@ -466,6 +466,9 @@ public static function updateAllSettingsGroupId() case 'System': // System not related with settings menu $id = null; break; + case 'Devlink': + $id = null; + break; default: // The default value if (preg_match('/^Email Server/', $setting->group)) { $id = SettingsMenus::getId(SettingsMenus::EMAIL_MENU_GROUP); diff --git a/resources/js/admin/auth-clients/components/AuthClientsListing.vue b/resources/js/admin/auth-clients/components/AuthClientsListing.vue index 34702a07ee..f69e7a6db4 100644 --- a/resources/js/admin/auth-clients/components/AuthClientsListing.vue +++ b/resources/js/admin/auth-clients/components/AuthClientsListing.vue @@ -29,6 +29,7 @@ @navigate="onNavigate" :actions="actions" :data="props.rowData" + :permission="permission" :divider="true" /> @@ -46,6 +47,10 @@ {{ props.rowData.secret.substr(0, 10) }}... + @@ -46,6 +48,15 @@ const props = defineProps({ title: { type: String, required: true + }, + type: { + type: String, + required: false + }, + disabled: { + type: Boolean, + required: false, + default: false } }); diff --git a/resources/js/admin/devlink/components/BundleDetail.vue b/resources/js/admin/devlink/components/BundleDetail.vue index 8a34d94d81..218a10debc 100644 --- a/resources/js/admin/devlink/components/BundleDetail.vue +++ b/resources/js/admin/devlink/components/BundleDetail.vue @@ -82,6 +82,7 @@ @@ -89,8 +90,10 @@ { ProcessMaker.apiClient.post(`devlink/local-bundles/${bundle.value.id}/add-settings`, { setting: event.key, config: null, + type: event.type, }) .then(() => { loadAssets(); diff --git a/resources/js/admin/devlink/components/platformConfigurations.js b/resources/js/admin/devlink/components/platformConfigurations.js index 1f78d359c4..61d477b21c 100644 --- a/resources/js/admin/devlink/components/platformConfigurations.js +++ b/resources/js/admin/devlink/components/platformConfigurations.js @@ -16,12 +16,16 @@ export default [ name: "UI Settings", }, { - type: "script_executors", - name: "Script Executors", + type: "ui_dashboards", + name: "Dashboards", + }, + { + type: "ui_menus", + name: "Menus", }, { - type: "analytics_settings", - name: "Analytics Settings", + type: "script_executors", + name: "Script Executors", }, { type: "public_files", diff --git a/resources/js/admin/devlink/components/settings.js b/resources/js/admin/devlink/components/settings.js index 3d7593c7da..c961cbefc8 100644 --- a/resources/js/admin/devlink/components/settings.js +++ b/resources/js/admin/devlink/components/settings.js @@ -1,18 +1,18 @@ export default [ { - type: "email_settings", + type: "Email", name: "Email Settings", }, { - type: "log-in_auth", + type: "Log-In & Auth", name: "Log-in & Auth", }, { - type: "user_settings", + type: "User Settings", name: "User Settings", }, { - type: "integrations", + type: "Integrations", name: "Integrations", }, ]; diff --git a/resources/js/admin/groups/components/GroupsListing.vue b/resources/js/admin/groups/components/GroupsListing.vue index 34ab46d279..695ae82fbe 100644 --- a/resources/js/admin/groups/components/GroupsListing.vue +++ b/resources/js/admin/groups/components/GroupsListing.vue @@ -33,6 +33,10 @@ /> + + + + + import datatableMixin from "../../components/common/mixins/datatable"; import dataLoadingMixin from "../../components/common/mixins/apiDataLoading"; +import AddToBundle from "../../components/shared/AddToBundle.vue"; import { createUniqIdsMixin } from "vue-uniq-ids"; const uniqIdsMixin = createUniqIdsMixin(); export default { mixins: [datatableMixin, dataLoadingMixin, uniqIdsMixin], + components: { AddToBundle }, props: ["filter", "permission"], data() { return { @@ -508,6 +523,9 @@ export default { this.loading = false; }); }, + onAddToBundle(data) { + this.$root.$emit('add-to-bundle', data); + }, }, }; diff --git a/resources/js/admin/settings/components/SettingsMenuCollapse.vue b/resources/js/admin/settings/components/SettingsMenuCollapse.vue index 13cec06982..f2eec464fd 100644 --- a/resources/js/admin/settings/components/SettingsMenuCollapse.vue +++ b/resources/js/admin/settings/components/SettingsMenuCollapse.vue @@ -20,11 +20,18 @@ aria-expanded="false" :aria-controls="`collapseOne${menu.id}`" > + {{ menu.menu_group }} - + @@ -50,11 +57,20 @@ + @@ -223,3 +255,10 @@ export default { font-weight: 700; } + diff --git a/resources/js/admin/users/components/UsersListing.vue b/resources/js/admin/users/components/UsersListing.vue index 313966ada7..c5daaf0af4 100644 --- a/resources/js/admin/users/components/UsersListing.vue +++ b/resources/js/admin/users/components/UsersListing.vue @@ -36,6 +36,10 @@ /> + { selected.value = null; error.value = null; assetId.value = data.id; + assetName.value = data.name || data.title; modal.value.show(); }); const save = (event) => { event.preventDefault(); if (selected.value?.id) { - const asset = { - 'type': props.assetType, - 'id': assetId.value - }; - window.ProcessMaker.apiClient.post(`devlink/local-bundles/${selected.value.id}/add-assets`, asset).then(() => { - modal.value.hide(); - window.ProcessMaker.alert(vue.$t('Asset added to bundle'), 'success'); - }).catch(e => { - error.value = e.response?.data?.error?.message || e.message; - }) + if (props.setting) { + window.ProcessMaker.apiClient.post(`devlink/local-bundles/${selected.value.id}/add-settings`, { + setting: props.assetType, + config: assetId.value, + type: props.settingType || null + }) + .then(() => { + window.ProcessMaker.alert(vue.$t('Setting added to bundle'), 'success'); + }); + } else { + const asset = { + 'type': props.assetType, + 'id': assetId.value + }; + window.ProcessMaker.apiClient.post(`devlink/local-bundles/${selected.value.id}/add-assets`, asset).then(() => { + modal.value.hide(); + window.ProcessMaker.alert(vue.$t('Asset added to bundle'), 'success'); + }).catch(e => { + error.value = e.response?.data?.error?.message || e.message; + }) + } } if (Array.isArray(selected.value)) { let bundles = []; selected.value.forEach((item) => { bundles.push(item.id); }); - const asset = { - 'type': props.assetType, - 'id': assetId.value, - 'bundles': bundles - }; - window.ProcessMaker.apiClient.post(`devlink/local-bundles/add-asset-to-bundles`, asset).then(() => { - modal.value.hide(); - window.ProcessMaker.alert(vue.$t('Asset added to bundle'), 'success'); - }).catch(e => { - error.value = e.response?.data?.error?.message || e.message; - }); + if (props.setting) { + const setting = { + 'setting': props.assetType, + 'config': assetId.value, + 'bundles': bundles, + 'type': props.settingType || null + }; + window.ProcessMaker.apiClient.post(`devlink/local-bundles/add-setting-to-bundles`, setting).then(() => { + modal.value.hide(); + window.ProcessMaker.alert(vue.$t('Setting added to bundle'), 'success'); + }).catch(e => { + error.value = e.response?.data?.error?.message || e.message; + }); + } else { + const asset = { + 'type': props.assetType, + 'id': assetId.value, + 'bundles': bundles + }; + window.ProcessMaker.apiClient.post(`devlink/local-bundles/add-asset-to-bundles`, asset).then(() => { + modal.value.hide(); + window.ProcessMaker.alert(vue.$t('Asset added to bundle'), 'success'); + }).catch(e => { + error.value = e.response?.data?.error?.message || e.message; + }); + } } }; @@ -71,7 +106,7 @@ const save = (event) => { >

{{ vue.$t('The asset') }} - {{ vue.$t('Marketing screens') }} + {{ assetName }} {{ vue.$t('can be added to one or various bundles.') }}

{{ vue.$t('Bundles') }} @@ -96,4 +131,4 @@ const save = (event) => { .add-to-bundle-modal .modal-footer { border-top: none; } - \ No newline at end of file + diff --git a/resources/views/admin/auth-clients/index.blade.php b/resources/views/admin/auth-clients/index.blade.php index 39651acf3c..926a39e92b 100644 --- a/resources/views/admin/auth-clients/index.blade.php +++ b/resources/views/admin/auth-clients/index.blade.php @@ -68,7 +68,7 @@
- +
diff --git a/routes/api.php b/routes/api.php index 2f749a61ac..4dce57a5fc 100644 --- a/routes/api.php +++ b/routes/api.php @@ -399,11 +399,13 @@ Route::post('devlink/local-bundles/{bundle}/add-assets', [DevLinkController::class, 'addAsset'])->name('devlink.add-asset'); Route::post('devlink/local-bundles/{bundle}/add-settings', [DevLinkController::class, 'addSettings'])->name('devlink.add-settings'); Route::post('devlink/local-bundles/add-asset-to-bundles', [DevLinkController::class, 'addAssetToBundles'])->name('devlink.add-asset-to-bundles'); + Route::post('devlink/local-bundles/add-setting-to-bundles', [DevLinkController::class, 'addSettingToBundles'])->name('devlink.add-setting-to-bundles'); Route::delete('devlink/local-bundles/{bundle}', [DevLinkController::class, 'deleteBundle'])->name('devlink.delete-bundle'); Route::delete('devlink/local-bundles/assets/{bundle_asset}', [DevLinkController::class, 'deleteBundleAsset'])->name('devlink.delete-bundle-asset'); Route::delete('devlink/local-bundles/settings/{bundle_setting}', [DevLinkController::class, 'deleteBundleSetting'])->name('devlink.delete-bundle-setting'); Route::get('devlink/export-local-bundle/{bundle}', [DevLinkController::class, 'exportLocalBundle'])->name('devlink.export-local-bundle'); Route::get('devlink/export-local-bundle/{bundle}/settings', [DevLinkController::class, 'exportLocalBundleSettings'])->name('devlink.export-local-bundle-settings'); + Route::get('devlink/export-local-bundle/{bundle}/settings-payloads', [DevLinkController::class, 'exportLocalBundleSettingPayloads'])->name('devlink.export-local-bundle-setting-payloads'); Route::get('devlink/export-local-asset', [DevLinkController::class, 'exportLocalAsset'])->name('devlink.export-local-asset'); Route::post('devlink/{devLink}/remote-bundles/{remoteBundleId}/install', [DevLinkController::class, 'installRemoteBundle'])->name('devlink.install-remote-bundle'); diff --git a/tests/Feature/ImportExport/ManifestTest.php b/tests/Feature/ImportExport/ManifestTest.php index 023a062a9d..0ac76f2eed 100644 --- a/tests/Feature/ImportExport/ManifestTest.php +++ b/tests/Feature/ImportExport/ManifestTest.php @@ -147,7 +147,7 @@ public function testWarningIfExporterClassMissing() ], ]; - Manifest::fromArray($payload, new Options([]), new Logger($user->id)); + Manifest::fromArray($payload, new Options([]), new Logger($user->id), $user->uuid); Event::assertDispatched(ImportLog::class, function ($event) { return $event->message === 'Class My\Test\Package does not exist.'; @@ -168,7 +168,7 @@ public function testWarningIfModelClassMissing() ], ]; - Manifest::fromArray($payload, new Options([]), new Logger($user->id)); + Manifest::fromArray($payload, new Options([]), new Logger($user->id), $user->uuid); Event::assertDispatched(ImportLog::class, function ($event) { return $event->message === 'Class Some\Other\Class does not exist.'; @@ -192,7 +192,7 @@ public function testWarningIfUnknownColumn() ], ]; - Manifest::fromArray($payload, new Options([]), new Logger($user->id)); + Manifest::fromArray($payload, new Options([]), new Logger($user->id), $user->uuid); Event::assertDispatched(ImportLog::class, function ($event) { return $event->message === "Attribute 'a_missing_column' does not exist in the table 'screens'"; diff --git a/tests/Model/DevLinkTest.php b/tests/Model/DevLinkTest.php index 5a35e8356b..dcec8f0c75 100644 --- a/tests/Model/DevLinkTest.php +++ b/tests/Model/DevLinkTest.php @@ -7,6 +7,7 @@ use ProcessMaker\Models\Bundle; use ProcessMaker\Models\DevLink; use ProcessMaker\Models\Screen; +use ProcessMaker\Models\User; use Tests\TestCase; class DevLinkTest extends TestCase @@ -60,10 +61,14 @@ public function testInstallRemoteBundle() $screen1 = Screen::factory()->create(['title' => 'Screen 1']); $screen2 = Screen::factory()->create(['title' => 'Screen 2']); + + $user1 = User::factory()->create(); + $bundle = Bundle::factory()->create([]); $bundle->syncAssets([$screen1, $screen2]); - + $bundle->addSettings('users', $user1->id); $exports = $bundle->export(); + $exportsSettingsPayloads = $bundle->exportSettingPayloads(); $screen1->delete(); $screen2->delete(); @@ -85,6 +90,9 @@ public function testInstallRemoteBundle() 'config' => null, ]], ]), + 'http://remote-instance.test/api/1.0/devlink/export-local-bundle/123/settings-payloads' => Http::response([ + 'payloads' => $exportsSettingsPayloads, + ]), ]); $devLink = DevLink::factory()->create([ @@ -147,8 +155,12 @@ public function testUpdateBundle() // Remote Instance $screen = Screen::factory()->create(['title' => 'Screen Name']); $bundle = Bundle::factory()->create([]); + $user1 = User::factory()->create(); + $bundle->addSettings('users', $user1->id); $bundle->syncAssets([$screen]); $exports = $bundle->export(); + $exportsSettingsPayloads = $bundle->exportSettingPayloads(); + $screenUuid = $screen->uuid; $screen->delete(); @@ -222,6 +234,9 @@ public function testUpdateBundle() 'config' => null, ]], ]), + 'http://remote-instance.test/api/1.0/devlink/export-local-bundle/123/settings-payloads' => Http::response([ + 'payloads' => $exportsSettingsPayloads, + ]), ]); $devLink->installRemoteBundle(123, 'update');