Skip to content

Commit

Permalink
[6.x] Manage blueprints in the Control Panel (#373)
Browse files Browse the repository at this point in the history
* Register blueprints

* Add update script to migrate existing blueprints

* Move blueprints into physical YAML files

* Adjust how blueprints are mocked in tests

* Remove blueprint from `runway:resources` command

* Update tests

* Fix final failing test

* Ensure blueprints already exist

* Add tests to cover updates to Resource

* Fix styling

* Move migrate blueprint logic into a command

* wip

* wip

* Update documentation

* Fix styling

* Fix styling

* Fix new tests that are failing due to blueprint changes

* Delete old blueprints at the end of the migration script

* Specify minimum version of statamic/cms

* Run Laravel Pint

---------

Co-authored-by: duncanmcclean <duncanmcclean@users.noreply.github.com>
  • Loading branch information
duncanmcclean and duncanmcclean authored Jan 12, 2024
1 parent 693f397 commit 3560aff
Show file tree
Hide file tree
Showing 24 changed files with 416 additions and 489 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"php": "^8.1",
"doctrine/dbal": "^4.0@RC",
"pixelfear/composer-dist-plugin": "^0.1.5",
"statamic/cms": "^4.0"
"statamic/cms": "^4.44"
},
"require-dev": {
"nunomaduro/collision": "^6.1",
Expand Down
43 changes: 7 additions & 36 deletions docs/blueprints.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,13 @@ title: 'Blueprints'

As explained in the [Statamic Docs](https://statamic.dev/blueprints#content), Blueprints are a key component to the content modeling process. They let you define the fields that should be available in the Control Panel and the way your data is stored.

### Creating & managing blueprints
## Creating & managing blueprints

Unfortunately, it's not yet possible to manage Runway blueprints in the Control Panel as there's no way for addons to "register" their own blueprints.
Every resource will have it's own blueprint. Just like with collections, you can manage the blueprints in the Control Panel.

In the meantime, you can create a blueprint for a collection, then move the outputted YAML file to the `resources/blueprints` directory.
![Runway blueprints in the Control Panel](/img/runway/runway-blueprints-in-the-cp.png)

:::note Note!
Remember that the field handles in your blueprint should match up exactly with the column names in the database, otherwise bad things will happen.
:::

Now, to use the blueprint you just created, simply specify it's "namespace" (usually just its filename, minus the `.yaml` extension) as a `blueprint` key in your resources's config array:

```php
'resources' => [
\App\Models\Order::class => [
'name' => 'Orders',
'blueprint' => 'order',
],
],
```

If you want to store your resource's blueprint inside a directory, like `resources/blueprints/runway`, you'll need to specify the blueprint as `runway.blueprint_name`.
When configuring fields, make sure that the field handles in your blueprint should match up *exactly* with the column names in the database, otherwise bad things will happen. You'll also want to ensure the database column type matches the fieldtype you're trying to use (see [Supported Fieldtypes](#supported-fieldtypes)).

## Supported Fieldtypes

Expand Down Expand Up @@ -76,15 +61,7 @@ Float|`float`|

## Nesting fields inside JSON columns

To avoid creating a migration for every new field you add to a blueprint, fields can be stored within a JSON column. To do so, use the `->` symbol within the field handle:

```yaml
fields:
-
handle: 'values->excerpt'
field:
type: text
```
To avoid creating a migration for every new field you add to a blueprint, fields can be stored within JSON columns. Simply use `->` within the field handle, like `values->excerpt`.

Your table will need to have a suitable column:

Expand Down Expand Up @@ -161,12 +138,6 @@ public function fullName(): Attribute
}
```

Then, in your user blueprint, you'd define `visibility: computed` in the field's config, like you would normally:
Then, in your user blueprint, you'd set the field's visibility to "Computed":

```yaml
-
handle: full_name
field:
type: text
visibility: computed
```
![Field's visibility set to computed](/img/runway/field-visibility-computed.png)
Binary file added docs/images/field-visibility-computed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/runway-blueprints-in-the-cp.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 6 additions & 9 deletions src/Console/Commands/GenerateBlueprint.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use Statamic\Console\RunsInPlease;
use Statamic\Facades\Blueprint;

class GenerateBlueprint extends Command
{
Expand Down Expand Up @@ -228,13 +227,11 @@ protected function generateNewBlueprint(Resource $resource, array $fields)
}
});

Blueprint::make($resource->handle())
->setContents([
'tabs' => [
'main' => ['fields' => $mainSection],
'sidebar' => ['fields' => $sidebarSection],
],
])
->save();
$resource->blueprint()->setContents([
'tabs' => [
'main' => ['fields' => $mainSection],
'sidebar' => ['fields' => $sidebarSection],
],
])->save();
}
}
3 changes: 1 addition & 2 deletions src/Console/Commands/ListResources.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,10 @@ public function handle()
}

$this->table(
['Handle', 'Model', 'Blueprint', 'Route'],
['Handle', 'Model', 'Route'],
Runway::allResources()->map(fn (Resource $resource) => [
$resource->handle(),
$resource->model()::class,
optional($resource->blueprint())->namespace().optional($resource->blueprint())->handle(),
$resource->hasRouting() ? $resource->route() : 'N/A',
])->values()->toArray()
);
Expand Down
72 changes: 72 additions & 0 deletions src/Console/Commands/MigrateBlueprints.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace DoubleThreeDigital\Runway\Console\Commands;

use DoubleThreeDigital\Runway\Resource;
use DoubleThreeDigital\Runway\Runway;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Statamic\Console\RunsInPlease;
use Statamic\Facades\Blueprint;
use Statamic\Fields\Blueprint as FieldsBlueprint;

class MigrateBlueprints extends Command
{
use RunsInPlease;

/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'runway:migrate-blueprints';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Part of the v6.0 upgrade. Migrates your blueprints so they can be managed in the Control Panel.';

/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}

/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
Runway::allResources()
->each(function (Resource $resource) {
$originalBlueprint = $this->resolveOriginalBlueprint($resource);

$resource->blueprint()->setContents($originalBlueprint->contents())->save();

if (File::exists($originalBlueprint->path())) {
File::delete($originalBlueprint->path());
}
});
}

protected function resolveOriginalBlueprint(Resource $resource): FieldsBlueprint
{
if (is_string($resource->config()->get('blueprint'))) {
return Blueprint::find($resource->config()->get('blueprint'));
}

if (is_array($resource->config()->get('blueprint'))) {
return Blueprint::make()->setHandle($resource->handle())->setContents($resource->config()->get('blueprint'));
}

throw new \Exception("Could not resolve the original blueprint for the [{$resource->handle()}] resource.");
}
}
9 changes: 7 additions & 2 deletions src/Resource.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ public function __construct(
protected string $handle,
protected Model $model,
protected string $name,
protected Blueprint $blueprint,
protected Collection $config
) {
}
Expand Down Expand Up @@ -50,7 +49,13 @@ public function plural(): string

public function blueprint(): Blueprint
{
return $this->blueprint;
$blueprint = Blueprint::find("runway::{$this->handle}");

if (! $blueprint) {
$blueprint = Blueprint::make($this->handle)->setNamespace('runway')->save();
}

return $blueprint;
}

public function config(): Collection
Expand Down
27 changes: 0 additions & 27 deletions src/Runway.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Statamic\Fields\Blueprint;

class Runway
{
Expand All @@ -16,44 +15,18 @@ public static function discoverResources(): self
{
static::$resources = collect(config('runway.resources'))
->mapWithKeys(function ($config, $model) {
$blueprint = null;
$config = collect($config);

$handle = $config->get('handle', Str::lower(class_basename($model)));

throw_if(
! in_array(Traits\HasRunwayResource::class, class_uses_recursive($model)),
new \Exception(__('The HasRunwayResource trait is missing from the [:model] model.', ['model' => $model]))
);

throw_if(
! $config->has('blueprint'),
new \Exception(__('The [:model] model is missing a blueprint.', ['model' => $model]))
);

if (is_string($config->get('blueprint'))) {
try {
$blueprint = Blueprint::find($config['blueprint']);
} catch (\Exception $e) {
// If we're running in a console & the blueprint doesn't exist, let's ignore the resource.
// https://github.com/duncanmcclean/runway/pull/320
if (app()->runningInConsole()) {
return [$handle => null];
}

throw $e;
}
}

if (is_array($config->get('blueprint'))) {
$blueprint = Blueprint::make()->setHandle($handle)->setContents($config['blueprint']);
}

$resource = new Resource(
handle: $handle,
model: $model instanceof Model ? $model : new $model(),
name: $config['name'] ?? Str::title($handle),
blueprint: $blueprint,
config: $config,
);

Expand Down
14 changes: 14 additions & 0 deletions src/ServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Traits\Conditionable;
use Statamic\API\Middleware\Cache;
use Statamic\Facades\Blueprint;
use Statamic\Facades\CP\Nav;
use Statamic\Facades\GraphQL;
use Statamic\Facades\Permission;
Expand All @@ -32,6 +33,7 @@ class ServiceProvider extends AddonServiceProvider
Console\Commands\GenerateBlueprint::class,
Console\Commands\GenerateMigration::class,
Console\Commands\ListResources::class,
Console\Commands\MigrateBlueprints::class,
Console\Commands\RebuildUriCache::class,
];

Expand All @@ -54,6 +56,7 @@ class ServiceProvider extends AddonServiceProvider

protected $updateScripts = [
UpdateScripts\ChangePermissionNames::class,
UpdateScripts\MigrateBlueprints::class,
];

protected $vite = [
Expand All @@ -79,13 +82,15 @@ public function boot()
], 'runway-config');

Statamic::booted(function () {

Runway::discoverResources();

$this
->registerRouteBindings()
->registerPermissions()
->registerPolicies()
->registerNavigation()
->registerBlueprints()
->registerSearchProvider()
->bootGraphQl()
->bootApi()
Expand Down Expand Up @@ -154,6 +159,15 @@ protected function registerNavigation(): self
return $this;
}

protected function registerBlueprints(): self
{
Blueprint::addNamespace('runway', base_path('resources/blueprints/runway'));

Runway::allResources()->each(fn (Resource $resource) => $resource->blueprint());

return $this;
}

protected function bootGraphQl(): self
{
Runway::allResources()
Expand Down
19 changes: 19 additions & 0 deletions src/UpdateScripts/MigrateBlueprints.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace DoubleThreeDigital\Runway\UpdateScripts;

use Illuminate\Support\Facades\Artisan;
use Statamic\UpdateScripts\UpdateScript;

class MigrateBlueprints extends UpdateScript
{
public function shouldUpdate($newVersion, $oldVersion)
{
return $this->isUpdatingTo('6.0.0');
}

public function update()
{
Artisan::call('runway:migrate-blueprints');
}
}
8 changes: 4 additions & 4 deletions tests/Console/Commands/GenerateBlueprintTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ public function can_generate_blueprint()

Blueprint::shouldReceive('find')->with('')->andReturnNull();

Blueprint::shouldReceive('make')
->with('post')
Blueprint::shouldReceive('find')
->with('runway::post')
->andReturn(new FieldsBlueprint('post'))
->shouldReceive('setContents')
->with([
Expand Down Expand Up @@ -130,8 +130,8 @@ public function can_generate_resource_with_column_that_can_not_be_matched_to_a_f

Blueprint::shouldReceive('find')->with('')->andReturnNull();

Blueprint::shouldReceive('make')
->with('post')
Blueprint::shouldReceive('find')
->with('runway::post')
->andReturn(new FieldsBlueprint('post'))
->shouldReceive('setContents')
->with([
Expand Down
Loading

0 comments on commit 3560aff

Please sign in to comment.