Skip to content

Commit

Permalink
Feature/custom config field support (#6)
Browse files Browse the repository at this point in the history
* refactor so configs related to user attributes can be fetched + better livewire support

* let devs add custom config fields

* remove changelog workflow
  • Loading branch information
luttje authored Dec 18, 2023
1 parent 4bf5066 commit b09fcb1
Show file tree
Hide file tree
Showing 23 changed files with 273 additions and 126 deletions.
31 changes: 0 additions & 31 deletions .github/workflows/update-changelog.yml

This file was deleted.

11 changes: 0 additions & 11 deletions CHANGELOG.md

This file was deleted.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ Let your users specify custom attributes for models in Filament, similar to Cust
- [x] Users can specify if attributes are required
- [x] Users can specify if attributes inherit their value from another attribute, even from a related model
- [x] For Resources
- [ ] For Livewire components
- [x] For Livewire components (if they implement the static `::getModel()` method)
- [x] For related fields that have a {relation}_id field in the form
- [ ] For related attributes that have no field in this form
- [x] User interface for managing user attributes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public function up()

// The resource or component to which the configurations apply.
$table->string('resource_type');
$table->string('model_type'); // For easy matching to the user_attributes table
$table->json('config');

// Ensure that each model can only have one set of configurations per resource.
Expand Down
10 changes: 5 additions & 5 deletions database/migrations/create_user_attributes_table.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@
public function up()
{
/**
* Contains the values for all user attributes on a resource to which
* Contains the values for all user attributes on a model to which
* a polymorphic relationship is attached.
*/
Schema::create('user_attributes', function (Blueprint $table) {
$table->id();

// Large enough morph for any type of id.
$table->string('resource_id');
$table->string('resource_type');
$table->string('model_id');
$table->string('model_type');
$table->json('values');

// Ensure that each resource can only have one set of attributes.
$table->unique(['resource_id', 'resource_type']);
// Ensure that each model can only have one set of attributes.
$table->unique(['model_id', 'model_type']);

$table->timestamps();
});
Expand Down
65 changes: 65 additions & 0 deletions docs/additional-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,68 @@ Besides using the package as described in the [README](../README.md) you can als
```

> You'll find all the attributes in the `user_attributes` table of your database. However you don't have to worry about it's existence. The `HasUserAttributes` trait handles all the database interactions for you.

## 🔎 Querying by config value

The following snippet shows how to get the config with all related user attributes that have the config value `should_import` set to `true`:

```php
$configs = UserAttributeConfig::queryByConfig('should_import', true)
->with('userAttributes.model')
->get();

// You'll get all the user attributes, so you still have to filter out those of other tenants.
```

And this snippet checks if there's a config with a `config` key using `queryByConfigKey`, it also limits to the current tenant:

```php
$tenant = user()->tenant ?? Filament::getTenant();
$configs = UserAttributeConfig::queryByConfigKey('import')
->where('owner_id', $tenant->id)
->where('owner_type', get_class($tenant))
->with('userAttributes.model')
->get();
```

## 📝 Add custom user attribute configuration fields

You can use `FilamentUserAttributes::registerUserAttributeConfigComponent($callbackOrComponent)` to add custom fields to the user attribute configuration form. This is useful if you want to add custom fields to the user attribute configuration form in `Luttje\FilamentUserAttributes\Filament\Resources\UserAttributeConfigResource`, but don't want to create a whole new resource for it.

```php
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Get;
use Illuminate\Support\ServiceProvider;
use Luttje\FilamentUserAttributes\Facades\FilamentUserAttributes;

class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
// ...

FilamentUserAttributes::registerUserAttributeConfigComponent(function(UserAttributeConfig $configModel) {
if ($configModel->model_type !== Product::class) {
return null;
}

return Fieldset::make(__('Import Settings'))
->schema([
Checkbox::make('import_enabled')
->label(__('Enable import'))
->default(false)
->live(),
TextInput::make('import_name')
->label(__('Name in import file'))
->hidden(fn(Get $get) => !$get('import_enabled'))
->default(''),
]);
});
}
}
```
7 changes: 6 additions & 1 deletion docs/manual-configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ Finally you need to show the user attributes configuration form somewhere. That

## 🎈 Filament Livewire Components

Filament Livewire components work roughly the same. We also implement the `UserAttributesConfigContract` method `getUserAttributesConfig` so the configuration is retrieved from the model that specifies configurations.
Filament Livewire components work roughly the same, but you additionally need to implement a static `getModel` method that returns the model class that should be used for the component. We also implement the `UserAttributesConfigContract` method `getUserAttributesConfig` so the configuration is retrieved from the model that specifies configurations.

```php
use Luttje\FilamentUserAttributes\Contracts\UserAttributesConfigContract;
Expand All @@ -141,6 +141,11 @@ class ProductManageComponent extends Component implements HasForms, HasTable, Us
{
$this->form->fill();
}

public static function getModel(): string
{
return Product::class;
}

public static function getUserAttributesConfig(): ?ConfiguresUserAttributesContract
{
Expand Down
2 changes: 2 additions & 0 deletions src/Contracts/UserAttributesConfigContract.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ public static function getAllTableColumns(): array;
public static function getFieldsForOrdering(): array;

public static function getColumnsForOrdering(): array;

public static function getModel(): string;
}
29 changes: 17 additions & 12 deletions src/Facades/FilamentUserAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,33 @@

namespace Luttje\FilamentUserAttributes\Facades;

use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Facade;

/**
* @method static void registerResources(array|Closure $resources)
* @method static void registerDefaultUserAttributeComponentFactories()
* @method static array getUserAttributeColumns(string $resource)
* @method static array getUserAttributeFields(string $resource)
* @method static ConfiguresUserAttributesContract getUserAttributeConfig(string $resource)
* @method static array getConfigurableResources(?bool $configuredOnly = true)
* @method static array getConfigurableModels(?bool $configuredOnly = true)
* @method static array discoverConfigurableResources(array $paths, bool $configuredOnly)
* @method static array addColumnBesidesColumn(array $columns, string $siblingColumnName, string $position, Column $columnToAdd)
* @method static array addFieldBesidesField(array $components, string $siblingComponentName, string $position, Component $componentToAdd, ?string $parentLabel = null)
* @method static array discoverConfigurableModels(array $paths, bool $configuredOnly)
* @method static string findModelFilePath(string $model)
* @method static string findResourceFilePath(string $resource)
* @method static array discoverConfigurableResources(array $paths, bool $configuredOnly)
* @method static array getAllFieldComponents(array $components, ?string $parentLabel = null)
* @method static array getAllTableColumns(array $columns)
* @method static array addFieldBesidesField(array $components, string $siblingComponentName, string $position, Component $componentToAdd, ?string $parentLabel = null)
* @method static array addColumnBesidesColumn(array $columns, string $siblingColumnName, string $position, Column $columnToAdd)
* @method static array getConfigurableModels(?bool $configuredOnly = true)
* @method static array getConfigurableResources(?bool $configuredOnly = true)
* @method static array getUserAttributeColumns(string $resource)
* @method static array getUserAttributeConfigComponents(UserAttributeConfig $configModel)
* @method static array getUserAttributeFields(string $resource)
* @method static array mergeCustomFormFields(array $fields, string $resource)
* @method static array mergeCustomTableColumns(array $columns, $resource)
* @method static Collection getResourcesByModel()
* @method static ConfiguresUserAttributesContract getUserAttributeConfig(string $resource)
* @method static string classNameToLabel(string $className)
* @method static string classNameToModelLabel(string $className)
* @method static string findModelFilePath(string $model)
* @method static string findResourceFilePath(string $resource)
* @method static string getModelFromResource(string $resource)
* @method static void registerDefaultUserAttributeComponentFactories()
* @method static void registerResources(array|Closure $resources)
* @method static void registerUserAttributeConfigComponent(Component|Closure $component)
*
* @see \Luttje\FilamentUserAttributes\FilamentUserAttributes
*/
Expand Down
4 changes: 2 additions & 2 deletions src/Filament/Factories/BaseComponentFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
abstract class BaseComponentFactory implements UserAttributeComponentFactoryInterface
{
public function __construct(
protected string $resource
protected string $modelType
) {
//
}
Expand Down Expand Up @@ -45,7 +45,7 @@ private function makeInheritedDefault(array $userAttribute, mixed $default = nul
}

if ($relatedField !== null) {
$record = $this->resource::getModel(); // TODO: Support Livewire components (which don't have the getModel method)
$record = $this->modelType;
$inheritRelationInfo = EloquentHelper::getRelationInfo($record, $userAttribute['inherit_relation']);
$related = ($inheritRelationInfo->relatedType)::find($relatedField);
$default = data_get($related, $userAttribute['inherit_attribute']);
Expand Down
5 changes: 3 additions & 2 deletions src/Filament/Resources/UserAttributeConfigResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace Luttje\FilamentUserAttributes\Filament\Resources;

use Filament\Forms;
use Filament\Forms\Set;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
Expand Down Expand Up @@ -39,7 +38,6 @@ public static function getEloquentQuery(): Builder
continue;
}

// TODO: Use a scope for always set this
$query->where('owner_type', get_class($config))
->where('owner_id', $config->getKey());

Expand All @@ -65,8 +63,11 @@ public static function resolveRecordRouteBinding(int | string $key): ?Model
->first();

if (!$userAttributeConfigs) {
$modelType = FilamentUserAttributes::getModelFromResource($resource);

$userAttributeConfigs = UserAttributeConfig::create([
'resource_type' => $resource,
'model_type' => $modelType,
'owner_type' => get_class($config),
'owner_id' => $config->id,
'config' => [],
Expand Down
2 changes: 1 addition & 1 deletion src/Filament/UserAttributeComponentFactoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

interface UserAttributeComponentFactoryInterface
{
public function __construct(string $resource);
public function __construct(string $modelType);

public function makeColumn(array $userAttribute): Column;

Expand Down
46 changes: 15 additions & 31 deletions src/Filament/UserAttributeComponentFactoryRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ public static function register(string $type, string $factory): void
static::$factories[$type] = $factory;
}

public static function getFactory(string $type, string $resource): UserAttributeComponentFactoryInterface
public static function getFactory(string $type, string $modelType): UserAttributeComponentFactoryInterface
{
if (!isset(static::$factories[$type])) {
throw new \Exception("Factory for type {$type} not registered.");
}

return new static::$factories[$type]($resource);
return new static::$factories[$type]($modelType);
}

public static function getRegisteredTypes(): array
Expand All @@ -52,25 +52,11 @@ public static function getRegisteredTypes(): array
*/
public static function getInheritRelationOptions(UserAttributeConfig $configModel): array
{
$resourceClass = $configModel->resource_type;
$resourceClassParents = class_parents($resourceClass);

if (!in_array(\Filament\Resources\Resource::class, $resourceClassParents)) {
// TODO: Support Livewire components
return [];
}

// TODO: CLean this up, possibly moving it somewhere else
// TODO: Clean this up, possibly moving it somewhere else
$resources = FilamentUserAttributes::getConfigurableResources();
$modelsMappedToResources = collect($resources)
->filter(function ($name, string $class) {
return method_exists($class, 'getModel');
})
->mapWithKeys(function ($name, string $class) {
return [$class::getModel() => $class];
});
$modelsMappedToResources = FilamentUserAttributes::getResourcesByModel();

$model = $resourceClass::getModel();
$model = $configModel->model_type;
$relations = [];

$self = new EloquentHelperRelationshipInfo();
Expand Down Expand Up @@ -137,7 +123,6 @@ function (Get $get) {
};
},
])
->readOnlyOn('edit')
->helperText(ucfirst(__('filament-user-attributes::user-attributes.name_help')))
->maxLength(255),
Forms\Components\Select::make('type')
Expand Down Expand Up @@ -183,9 +168,9 @@ function (Get $get) {
}

if ($inheritRelationName === '__self') {
$inheritRelatedModelType = $configModel->resource_type::getModel();
$inheritRelatedModelType = $configModel->model_type;
} else {
$model = $configModel->resource_type::getModel();
$model = $configModel->model_type;
$inheritRelationInfo = EloquentHelper::getRelationInfo($model, $inheritRelationName);

if (!$inheritRelationInfo) {
Expand All @@ -195,14 +180,7 @@ function (Get $get) {
$inheritRelatedModelType = $inheritRelationInfo->relatedType;
}

$resources = FilamentUserAttributes::getConfigurableResources();
$resource = collect($resources)
->filter(function ($name, string $class) {
return method_exists($class, 'getModel');
})
->mapWithKeys(function ($name, string $class) {
return [$class::getModel() => $class];
})
$resource = FilamentUserAttributes::getResourcesByModel()
->filter(function ($class, $model) use ($inheritRelatedModelType) {
return $model === $inheritRelatedModelType;
})
Expand All @@ -222,9 +200,15 @@ function (Get $get) {
])
]);

$customConfigFields = FilamentUserAttributes::getUserAttributeConfigComponents($configModel);

foreach ($customConfigFields as $customConfigField) {
$schemas[] = $customConfigField;
}

foreach (static::$factories as $type => $factoryClass) {
/** @var UserAttributeComponentFactoryInterface */
$factory = new $factoryClass($configModel->resource_type);
$factory = new $factoryClass($configModel->model_type);
$factorySchema = $factory->makeConfigurationSchema();

$schemas[] = Forms\Components\Fieldset::make('customizations_for_' . $type)
Expand Down
Loading

0 comments on commit b09fcb1

Please sign in to comment.