Skip to content

Commit

Permalink
[5.x] Add ability to reset namespaced blueprints (#9327)
Browse files Browse the repository at this point in the history
Co-authored-by: Jason Varga <[email protected]>
  • Loading branch information
ryanmitchell and jasonvarga authored Aug 19, 2024
1 parent 9fd729f commit ceb4096
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 0 deletions.
3 changes: 3 additions & 0 deletions resources/js/bootstrap/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import ResourceDeleter from '../components/ResourceDeleter.vue';
import Stack from '../components/stacks/Stack.vue';
import StackTest from '../components/stacks/StackTest.vue';
import CodeBlock from '../components/CodeBlock.vue';
import BlueprintResetter from '../components/blueprints/BlueprintResetter.vue';

// Third Party
Vue.component('v-select', vSelect)
Expand Down Expand Up @@ -142,3 +143,5 @@ Vue.component('resource-deleter', ResourceDeleter);

Vue.component('stack', Stack);
Vue.component('stack-test', StackTest);

Vue.component('blueprint-resetter', BlueprintResetter);
102 changes: 102 additions & 0 deletions resources/js/components/blueprints/BlueprintResetter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<template>
<confirmation-modal
v-if="resetting"
:title="modalTitle"
:bodyText="modalBody"
:buttonText="__('Reset')"
:danger="true"
@confirm="confirmed"
@cancel="cancel"
>
</confirmation-modal>
</template>

<script>
export default {
props: {
resource: {
type: Object
},
resourceTitle: {
type: String
},
route: {
type: String,
},
redirect: {
type: String
},
reload: {
type: Boolean
}
},
data() {
return {
resetting: false,
redirectFromServer: null,
}
},
computed: {
title() {
return data_get(this.resource, 'title', this.resourceTitle);
},
modalTitle() {
return __('Reset :resource', {resource: this.title});
},
modalBody() {
return __('Are you sure you want to reset this item?');
},
resetUrl() {
let url = data_get(this.resource, 'reset_url', this.route);
if (! url) console.error('BlueprintResetter cannot find reset url');
return url;
},
redirectUrl() {
return this.redirect || this.redirectFromServer;
},
},
methods: {
confirm() {
this.resetting = true;
},
confirmed() {
this.$axios.delete(this.resetUrl)
.then(response => {
this.redirectFromServer = data_get(response, 'data.redirect');
this.success();
})
.catch(() => {
this.$toast.error(__('Something went wrong'));
});
},
success() {
if (this.redirectUrl) {
location.href = this.redirectUrl;
return;
}
if (this.reload) {
location.reload();
return;
}
this.$toast.success(__('Reset'));
this.$emit('reset');
},
cancel() {
this.resetting = false;
}
}
}
</script>
14 changes: 14 additions & 0 deletions resources/views/blueprints/index.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,20 @@
<a href="{{ cp_route('blueprints.edit', [$blueprint['namespace'], $blueprint['handle']]) }}">{{ $blueprint['title'] }}</a>
</div>
</td>
<th class="actions-column">
@if ($blueprint['is_resettable'])
<dropdown-list class="dropdown-list">
<dropdown-item :text="__('Reset')" class="warning" @click="$refs[`resetter_{{ $blueprint['namespace'] }}_{{ $blueprint['handle'] }}`].confirm()">
<blueprint-resetter
ref="resetter_{{ $blueprint['namespace'] }}_{{ $blueprint['handle'] }}"
:resource='@json($blueprint)'
reload
>
</blueprint-resetter>
</dropdown-item>
</dropdown-list>
@endif
</td>
</tr>
@endforeach
</table>
Expand Down
1 change: 1 addition & 0 deletions routes/cp.php
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@
Route::get('blueprints', [BlueprintController::class, 'index'])->name('blueprints.index');
Route::get('blueprints/{namespace}/{handle}', [BlueprintController::class, 'edit'])->name('blueprints.edit');
Route::patch('blueprints/{namespace}/{handle}', [BlueprintController::class, 'update'])->name('blueprints.update');
Route::delete('blueprints/{namespace}/{handle}/reset', [BlueprintController::class, 'reset'])->name('blueprints.reset');
Route::get('fieldtypes', [FieldtypesController::class, 'index']);
});

Expand Down
20 changes: 20 additions & 0 deletions src/Events/BlueprintReset.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Statamic\Events;

use Statamic\Contracts\Git\ProvidesCommitMessage;

class BlueprintReset extends Event implements ProvidesCommitMessage
{
public $blueprint;

public function __construct($blueprint)
{
$this->blueprint = $blueprint;
}

public function commitMessage()
{
return __('Blueprint reset', [], config('statamic.git.locale'));
}
}
1 change: 1 addition & 0 deletions src/Events/Concerns/ListensForContentEvents.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ trait ListensForContentEvents
\Statamic\Events\AssetReuploaded::class,
\Statamic\Events\AssetReferencesUpdated::class,
\Statamic\Events\BlueprintDeleted::class,
\Statamic\Events\BlueprintReset::class,
\Statamic\Events\BlueprintSaved::class,
\Statamic\Events\CollectionDeleted::class,
\Statamic\Events\CollectionSaved::class,
Expand Down
21 changes: 21 additions & 0 deletions src/Fields/Blueprint.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Statamic\Events\BlueprintCreating;
use Statamic\Events\BlueprintDeleted;
use Statamic\Events\BlueprintDeleting;
use Statamic\Events\BlueprintReset;
use Statamic\Events\BlueprintSaved;
use Statamic\Events\BlueprintSaving;
use Statamic\Exceptions\DuplicateFieldException;
Expand Down Expand Up @@ -414,6 +415,12 @@ public function isDeletable()
return ! $this->isNamespaced();
}

public function isResettable()
{
return $this->isNamespaced()
&& File::exists($this->path());
}

public function toPublishArray()
{
return [
Expand Down Expand Up @@ -501,6 +508,15 @@ public function delete()
return true;
}

public function reset()
{
BlueprintRepository::reset($this);

BlueprintReset::dispatch($this);

return true;
}

public function ensureField($handle, $fieldConfig, $tab = null, $prepend = false)
{
return $this->ensureFieldInTab($handle, $fieldConfig, $tab, $prepend);
Expand Down Expand Up @@ -731,6 +747,11 @@ public function toQueryableValue()
return $this->handle();
}

public function resetUrl()
{
return cp_route('blueprints.reset', [$this->namespace(), $this->handle()]);
}

public function writeFile($path = null)
{
File::put($path ?? $this->buildPath(), $this->fileContents());
Expand Down
9 changes: 9 additions & 0 deletions src/Fields/BlueprintRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,15 @@ public function delete(Blueprint $blueprint)
$blueprint->deleteFile();
}

public function reset(Blueprint $blueprint)
{
if (! $blueprint->isNamespaced()) {
throw new \Exception('Non-namespaced blueprints cannot be reset');
}

File::delete($blueprint->path());
}

private function clearBlinkCaches()
{
Blink::store(self::BLINK_FOUND)->flush();
Expand Down
15 changes: 15 additions & 0 deletions src/Http/Controllers/CP/Fields/BlueprintController.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public function index()
'handle' => $blueprint->handle(),
'namespace' => $blueprint->namespace(),
'title' => $blueprint->title(),
'reset_url' => $blueprint->resetUrl(),
'is_resettable' => $blueprint->isResettable(),
];
})
->sortBy('title')
Expand Down Expand Up @@ -73,4 +75,17 @@ public function update(Request $request, $namespace, $handle)

$this->updateBlueprint($request, $blueprint);
}

public function reset($namespace, $handle)
{
$blueprint = Blueprint::find($namespace.'::'.$handle);

if (! $blueprint) {
throw new NotFoundHttpException;
}

$blueprint->reset();

return response('');
}
}
13 changes: 13 additions & 0 deletions tests/Fields/BlueprintRepositoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,19 @@ public function it_gets_blueprints_in_a_custom_namespace_with_overrides()
$this->assertEquals(['First Blueprint', 'Overridden Second Blueprint'], $blueprints->map->title()->values()->all());
}

#[Test]
public function it_resets_a_namespaced_blueprint()
{
File::shouldReceive('exists')->with('/path/to/resources/blueprints/vendor/foo/test.yaml')->andReturnTrue();
File::shouldReceive('get')->with('/path/to/resources/blueprints/vendor/foo/test.yaml')->once()->andReturn('title: Overwritten Test Blueprint');
File::shouldReceive('delete')->once();

$this->repo->addNamespace('foo', 'foo');
$blueprint = $this->repo->find('foo::test');

$this->repo->reset($blueprint);
}

#[Test]
public function it_sets_the_namespace_when_passed_when_making()
{
Expand Down

0 comments on commit ceb4096

Please sign in to comment.