Skip to content

Commit

Permalink
Add backend reinstall functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
nolanpro committed Oct 25, 2024
1 parent ac5e6fc commit 13cf54c
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 33 deletions.
1 change: 0 additions & 1 deletion ProcessMaker/Http/Controllers/Api/DevLinkController.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@ public function updateBundle(Request $request, Bundle $bundle)

$bundle->name = $request->input('name');
$bundle->published = (bool) $request->input('published', false);
$bundle->version = $bundle->version + 1;
$bundle->saveOrFail();

return $bundle;
Expand Down
87 changes: 85 additions & 2 deletions ProcessMaker/Models/Bundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use ProcessMaker\Exception\ExporterNotSupported;
use ProcessMaker\Exception\ValidationException;
use ProcessMaker\ImportExport\Exporter;
use ProcessMaker\ImportExport\Importer;
use ProcessMaker\ImportExport\Logger;
use ProcessMaker\ImportExport\Options;
use ProcessMaker\Models\ProcessMakerModel;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;

class Bundle extends ProcessMakerModel
class Bundle extends ProcessMakerModel implements HasMedia
{
use HasFactory;
use InteractsWithMedia;

protected $guarded = ['id'];

Expand Down Expand Up @@ -127,4 +132,82 @@ public function validateEditable()
throw ValidationException::withMessages(['*' => 'Bundle is not editable']);
}
}

public function filesSortedByVersion()
{
return $this->getMedia()->map(function ($media) {
return [
$media,
$media->getCustomProperty('version'),
];
})->sort(function ($a, $b) {
// newest versions first
return version_compare($a[1], $b[1]) * -1;
})->map(function ($item) {
return $item[0];
});
}

public function newestVersionFile()
{
return $this->filesSortedByVersion()->first();
}

public function savePayloadsToFile(array $payloads)
{
$this->addMediaFromString(
gzencode(
json_encode($payloads)
),
)->usingFileName('payloads.json.gz')
->withCustomProperties(['version' => $this->version])
->toMediaCollection();

// Keep only the 3 most recent versions
$count = 0;
foreach ($this->filesSortedByVersion() as $media) {
if ($count >= 3) {
$media->delete();
}
$count++;
}
}

public function install(array $payloads, $mode, $logger = null)
{
if ($logger === null) {
$logger = new Logger();
}

$logger->status('Saving the bundle locally');
$this->savePayloadsToFile($payloads);

$logger->status('Installing bundle on the this instance');
$logger->setSteps($payloads);

$options = new Options([
'mode' => $mode,
// 'saveAssetsMode' => 'saveAllAssets',
// 'isTemplate' => false,
]);
$assets = [];
foreach ($payloads as $payload) {
$assets[] = DevLink::import($payload, $options, $logger);
}

if ($mode === 'update') {
$logger->status('Syncing bundle assets');
$this->syncAssets($assets);
}
}

public function reinstall(string $mode)
{
$media = $this->newestVersionFile();

$content = file_get_contents($media->getPath());
$payloads = json_decode(gzdecode($content), true);

$this->install($payloads, $mode);
}
}
43 changes: 16 additions & 27 deletions ProcessMaker/Models/DevLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use ProcessMaker\ImportExport\Options;
use ProcessMaker\Models\ProcessMakerModel;
use Ramsey\Uuid\Type\Integer;
use ZipArchive;

class DevLink extends ProcessMakerModel
{
Expand Down Expand Up @@ -156,47 +157,35 @@ public function installRemoteBundle($bundleId, $updateType)
]
);

$this->logger->status('Installing bundle on the this instance');
$this->logger->setSteps($bundleExport['payloads']);

$options = [
'mode' => $updateType,
'saveAssetsMode' => 'saveAllAssets',
'isTemplate' => false,
];
$assets = [];
foreach ($bundleExport['payloads'] as $payload) {
$assets[] = $this->import($payload, $options);
}

if ($updateType === 'update') {
$this->logger->status('Syncing bundle assets');
$bundle->syncAssets($assets);
}
$bundle->install($bundleExport['payloads'], $updateType, $this->logger);

$this->logger->setStatus('done');
}

private function import(array $payload, $options = null)
{
$options = $options === null ? new Options([]) : new Options($options);
$importer = new Importer($payload, $options, $this->logger);
$manifest = $importer->doImport();

return $manifest[$payload['root']]->model;
}

public function installRemoteAsset(string $class, int $id) : ProcessMakerModel
{
$payload = $this->client()->get(
route('api.devlink.export-local-asset', ['class' => $class, 'id' => $id], false)
)->json();

return $this->import($payload);
$logger = new Logger();
$options = new Options([
'mode' => 'update',
]);

return self::import($payload, $options, $logger);
}

public function bundles()
{
return $this->hasMany(Bundle::class);
}

public static function import(array $payload, Options $options, Logger $logger)
{
$importer = new Importer($payload, $options, $logger);
$manifest = $importer->doImport();

return $manifest[$payload['root']]->model;
}
}
2 changes: 1 addition & 1 deletion resources/js/admin/devlink/components/UpdateBundle.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const executeUpdate = (updateType) => {
>
<b-form-radio value="update">
{{ $t('Update Bundle Assets') }}
<p class="text-muted">{{ $t('Existing assets on this instance will be updated.') }}</p>
<p class="text-muted">{{ $t('Existing assets on this instance will be updated. Warning: this will overwrite any changes you made to the assets on this instance.') }}</p>
</b-form-radio>
<b-form-radio value="copy">
Expand Down
2 changes: 1 addition & 1 deletion resources/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2408,7 +2408,7 @@
"Update Bundle Assets" : "Update Bundle Assets",
"Copy Bundle Assets" : "Copy Bundle Assets",
"Create new copies of bundle assets." : "Create new copies of bundle assets.",
"Existing assets on this instance will be updated." : "Existing assets on this instance will be updated.",
"Existing assets on this instance will be updated. Warning: this will overwrite any changes you made to the assets on this instance." : "Existing assets on this instance will be updated. Warning: this will overwrite any changes you made to the assets on this instance.",
"Select how you would like to update the bundle <strong>{{ selectedBundleName }}</strong>.": "Select how you would like to update the bundle <strong>{{ selectedBundleName }}</strong>.",
"Are you sure you increase the version of <strong>{{ selectedBundleName }}</strong>?": "Are you sure you increase the version of <strong>{{ selectedBundleName }}</strong>?"
}
2 changes: 1 addition & 1 deletion routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@
Route::get('devlink/export-local-bundle/{bundle}', [DevLinkController::class, 'exportLocalBundle'])->name('devlink.export-local-bundle');
Route::get('devlink/export-local-asset', [DevLinkController::class, 'exportLocalAsset'])->name('devlink.export-local-asset');

Route::post('devlink/{devLink}/remote-bundles/{removeBundleId}/install', [DevLinkController::class, 'installRemoteBundle'])->name('devlink.install-remote-bundle');
Route::post('devlink/{devLink}/remote-bundles/{remoteBundleId}/install', [DevLinkController::class, 'installRemoteBundle'])->name('devlink.install-remote-bundle');
Route::post('devlink/{devLink}/install-remote-asset', [DevLinkController::class, 'installRemoteAsset'])->name('devlink.install-remote-asset');

// Put these last to avoid conflicts with the other devlink routes
Expand Down
25 changes: 25 additions & 0 deletions tests/Model/BundleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,29 @@ public function testSyncAssets()
$this->assertEquals($screen1->id, $bundle->assets[0]->asset_id);
$this->assertEquals($screen3->id, $bundle->assets[1]->asset_id);
}

public function testReinstallBundle()
{
// Remote
$screen = Screen::factory()->create(['title' => 'Original Screen Name']);
$screenUuid = $screen->uuid;
$bundle = Bundle::factory()->create();
$bundle->syncAssets([$screen]);
$payloads = $bundle->export();

$bundle->delete();
$screen->delete();

// Local
$bundle = Bundle::factory()->create();
$bundle->install($payloads, 'copy');

$screen = Screen::where('uuid', $screenUuid)->firstOrFail();
$screen->title = 'New Screen Name';
$screen->save();

$this->assertEquals('New Screen Name', $screen->refresh()->title);
$bundle->reinstall('update');
$this->assertEquals('Original Screen Name', $screen->refresh()->title);
}
}
113 changes: 113 additions & 0 deletions tests/Model/DevLinkTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Tests\Model;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use ProcessMaker\Models\Bundle;
use ProcessMaker\Models\DevLink;
use ProcessMaker\Models\Screen;
Expand Down Expand Up @@ -55,6 +56,8 @@ public function testGetOauthRedirectUrl()

public function testInstallRemoteBundle()
{
Storage::fake('local');

$screen1 = Screen::factory()->create(['title' => 'Screen 1']);
$screen2 = Screen::factory()->create(['title' => 'Screen 2']);
$bundle = Bundle::factory()->create([]);
Expand Down Expand Up @@ -90,6 +93,13 @@ public function testInstallRemoteBundle()
$this->assertCount(2, $bundle->assets);
$this->assertEquals('Screen 1', $bundle->assets[0]->asset->title);
$this->assertEquals('Screen 2', $bundle->assets[1]->asset->title);

// test that we saved the payload
$media = $bundle->getMedia();
$this->assertCount(1, $media);
$gzPath = $media[0]->getPath();
$payloads = json_decode(gzdecode(file_get_contents($gzPath)), true);
$this->assertCount(2, $payloads);
}

public function testRemoteBundles()
Expand Down Expand Up @@ -123,4 +133,107 @@ public function testRemoteBundles()
$this->assertEquals($bundles['data'][0]['is_installed'], true);
$this->assertEquals($bundles['data'][1]['is_installed'], false);
}

public function testUpdateBundle()
{
Storage::fake('local');

// Remote Instance
$screen = Screen::factory()->create(['title' => 'Screen Name']);
$bundle = Bundle::factory()->create([]);
$bundle->syncAssets([$screen]);
$exports = $bundle->export();
$screenUuid = $screen->uuid;

$screen->delete();
$bundle->delete();

$exportsNewScreenName = $exports;
$exportsNewScreenName[0]['export'][$screen->uuid]['attributes']['title'] = 'Screen Name Updated';

// Local Instance
$devLink = DevLink::factory()->create([
'url' => 'http://remote-instance.test',
]);

$existingBundle = Bundle::factory()->create([
'dev_link_id' => $devLink->id,
'remote_id' => 123,
'version' => '1',
]);

Http::fake([
'http://remote-instance.test/api/1.0/devlink/local-bundles/123' => Http::sequence()
->push([
'id' => 123,
'name' => 'Test Bundle',
'published' => true,
'version' => '2',
], 200)
->push([
'id' => 123,
'name' => 'Test Bundle',
'published' => true,
'version' => '3',
], 200)
->push([
'id' => 123,
'name' => 'Test Bundle',
'published' => true,
'version' => '4',
], 200)
->push([
'id' => 123,
'name' => 'Test Bundle',
'published' => true,
'version' => '8',
], 200)
->push([
'id' => 123,
'name' => 'Test Bundle',
'published' => true,
'version' => '9',
], 200),
'http://remote-instance.test/api/1.0/devlink/export-local-bundle/123' => Http::sequence()
->push([
'payloads' => $exports,
], 200)
->push([
'payloads' => $exportsNewScreenName,
], 200)
->push([
'payloads' => $exportsNewScreenName,
], 200)
->push([
'payloads' => $exportsNewScreenName,
], 200)
->push([
'payloads' => $exportsNewScreenName,
], 200),
]);

$devLink->installRemoteBundle(123, 'update');
$screen = Screen::where('uuid', $screenUuid)->first();
$this->assertEquals('Screen Name', $screen->title);

$devLink->installRemoteBundle(123, 'update');
$screen->refresh();
$this->assertEquals('Screen Name Updated', $screen->title);

// Check saved media
$media = $existingBundle->getMedia();
$this->assertCount(2, $media);
$this->assertEquals($media[0]->getCustomProperty('version'), '2');
$this->assertEquals($media[1]->getCustomProperty('version'), '3');

// only the latest 3 versions should be saved
$devLink->installRemoteBundle(123, 'update');
$devLink->installRemoteBundle(123, 'update');
$devLink->installRemoteBundle(123, 'update');

$media = $existingBundle->refresh()->getMedia();
$this->assertCount(3, $media);
$savedVersions = $media->map(fn ($m) => $m->getCustomProperty('version'))->toArray();
$this->assertEquals(['4', '8', '9'], $savedVersions);
}
}

0 comments on commit 13cf54c

Please sign in to comment.