Skip to content
Permalink
Browse files

Merge pull request #601 from ReeceM/feature/log-json-attributes

Feature/log json attributes
  • Loading branch information...
Gummibeer committed Oct 6, 2019
2 parents a63f6c3 + 76b9810 commit a28239bceeb0dd51246719bfe378c4f0a62a9cf2
@@ -200,6 +200,84 @@ class NewsItem extends Model
Changing only `name` means only the `name` attribute will be logged in the activity, and `text` will be left out.



## Logging only a specific JSON attribute sub-key

If you would like to log only the changes to a specific JSON objects sub-keys. You can use the same method for logging specific columns with the difference of choosing the json key to log.

```php
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Traits\LogsActivity;
class NewsItem extends Model
{
use LogsActivity;
protected $fillable = ['preferences', 'name'];
protected static $logAttributes = ['preferences->notifications->status', 'preferences->hero_url'];
protected $casts = [
'preferences' => 'collection' // casting the JSON database column
];
}
```

Changing only `preferences->notifications->status` or `preferences->hero_url` means only the `preferences->notifications->status` or `preferences->hero_url` attribute will be logged in the activity, and everything else `preferences` will be left out.

The output of this in a activity entry would be as follows:

```php
// Create a news item.
$newsItem = NewsItem::create([
'name' => 'Title',
'preferences' => [
'notifications' => [
'status' => 'on',
],
'hero_url' => ''
],
]);
// Update the json object
$newsItem->update([
'preferences' => [
'notifications' => [
'status' => 'on',
],
'hero_url' => 'http://example.com/hero.png'
],
]);
$lastActivity = Activity::latest()->first();
$lastActivity->properties->toArray();
```

```php
// output
[
"attributes" => [
"preferences" => [ // the updated values
"notifications" => [
"status" => "on",
],
"hero_url" => "http://example.com/hero.png",
],
],
"old" => [
"preferences" => [ // the old settings
"notifications" => [
"status" => "off",
],
"hero_url" => "",
],
],
]
```

The result in the log entry key for the attribute will be what is in the `$logAttributes`.

## Prevent save logs items that have no changed attribute

Setting `$submitEmptyLogs` to `false` prevents the package from storing empty logs. Storing empty logs can happen when you only want to log a certain attribute but only another changes.
@@ -2,6 +2,7 @@
namespace Spatie\Activitylog\Traits;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Exceptions\CouldNotLogChanges;
@@ -125,6 +126,12 @@ public static function logChanges(Model $model): array
foreach ($attributes as $attribute) {
if (Str::contains($attribute, '.')) {
$changes += self::getRelatedModelAttributeValue($model, $attribute);
} elseif (Str::contains($attribute, '->')) {
Arr::set(
$changes,
str_replace('->', '.', $attribute),
static::getModelAttributeJsonValue($model, $attribute)
);
} else {
$changes[$attribute] = $model->getAttribute($attribute);
@@ -156,4 +163,13 @@ protected static function getRelatedModelAttributeValue(Model $model, string $at
return ["{$relatedModelName}.{$relatedAttribute}" => $relatedModel->$relatedAttribute ?? null];
}
protected static function getModelAttributeJsonValue(Model $model, string $attribute)
{
$path = explode('->', $attribute);
$modelAttribute = array_shift($path);
$modelAttribute = collect($model->getAttribute($modelAttribute));
return data_get($modelAttribute, implode('.', $path));
}
}
@@ -1121,6 +1121,244 @@ public function it_can_use_nullable_date_as_loggable_attributes()
$this->assertEquals($expectedChanges, $this->getLastActivity()->changes()->toArray());
}
/** @test */
public function it_can_store_the_changes_of_json_attributes()
{
$articleClass = new class() extends Article {
protected static $logAttributes = ['name', 'json->data'];
public static $logOnlyDirty = true;
protected $casts = [
'json' => 'collection',
];
use LogsActivity;
};
$article = new $articleClass();
$article->json = ['data' => 'test'];
$article->name = 'I am JSON';
$article->save();
$expectedChanges = [
'attributes' => [
'name' => 'I am JSON',
'json' => [
'data' => 'test',
],
],
];
$changes = $this->getLastActivity()->changes()->toArray();
$this->assertSame($expectedChanges, $changes);
}
/** @test */
public function it_will_not_store_changes_to_untracked_json()
{
$articleClass = new class() extends Article {
protected static $logAttributes = ['name', 'json->data'];
public static $logOnlyDirty = true;
protected $casts = [
'json' => 'collection',
];
use LogsActivity;
};
$article = new $articleClass();
$article->json = ['unTracked' => 'test'];
$article->name = 'a name';
$article->save();
$article->name = 'I am JSON';
$article->json = ['unTracked' => 'different string'];
$article->save();
$expectedChanges = [
'attributes' => [
'name' => 'I am JSON',
],
'old' => [
'name' => 'a name',
],
];
$changes = $this->getLastActivity()->changes()->toArray();
$this->assertSame($expectedChanges, $changes);
}
/** @test */
public function it_will_return_null_for_missing_json_attribute()
{
$articleClass = new class() extends Article {
protected static $logAttributes = ['name', 'json->data->missing'];
public static $logOnlyDirty = true;
protected $casts = [
'json' => 'collection',
];
use LogsActivity;
};
$jsonToStore = [];
$article = new $articleClass();
$article->json = $jsonToStore;
$article->name = 'I am JSON';
$article->save();
data_set($jsonToStore, 'data.missing', 'I wasn\'t here');
$article->json = $jsonToStore;
$article->save();
$expectedChanges = [
'attributes' => [
'json' => [
'data' => [
'missing' => 'I wasn\'t here',
],
],
],
'old' => [
'json' => [
'data' => [
'missing' => null,
],
],
],
];
$changes = $this->getLastActivity()->changes()->toArray();
$this->assertSame($expectedChanges, $changes);
}
/** @test */
public function it_will_return_an_array_for_sub_key_in_json_attribute()
{
$articleClass = new class() extends Article {
protected static $logAttributes = ['name', 'json->data'];
public static $logOnlyDirty = true;
protected $casts = [
'json' => 'collection',
];
use LogsActivity;
};
$jsonToStore = [
'data' => [
'data_a' => 1,
'data_b' => 2,
'data_c' => 3,
'data_d' => 4,
'data_e' => 5,
],
];
$article = new $articleClass();
$article->json = $jsonToStore;
$article->name = 'I am JSON';
$article->save();
data_set($jsonToStore, 'data.data_c', 'I Got The Key');
$article->json = $jsonToStore;
$article->save();
$expectedChanges = [
'attributes' => [
'json' => [
'data' => [
'data_a' => 1,
'data_b' => 2,
'data_c' => 'I Got The Key',
'data_d' => 4,
'data_e' => 5,
],
],
],
'old' => [
'json' => [
'data' => [
'data_a' => 1,
'data_b' => 2,
'data_c' => 3,
'data_d' => 4,
'data_e' => 5,
],
],
],
];
$changes = $this->getLastActivity()->changes()->toArray();
$this->assertSame($expectedChanges, $changes);
}
/** @test */
public function it_will_access_further_than_level_one_json_attribute()
{
$articleClass = new class() extends Article {
protected static $logAttributes = ['name', 'json->data->can->go->how->far'];
public static $logOnlyDirty = true;
protected $casts = [
'json' => 'collection',
];
use LogsActivity;
};
$jsonToStore = [];
// data_set($jsonToStore, 'data.can.go.how.far', 'Data');
$article = new $articleClass();
$article->json = $jsonToStore;
$article->name = 'I am JSON';
$article->save();
data_set($jsonToStore, 'data.can.go.how.far', 'This far');
$article->json = $jsonToStore;
$article->save();
$expectedChanges = [
'attributes' => [
'json' => [
'data' => [
'can' => [
'go' => [
'how' => [
'far' => 'This far',
],
],
],
],
],
],
'old' => [
'json' => [
'data' => [
'can' => [
'go' => [
'how' => [
'far' => null,
],
],
],
],
],
],
];
$changes = $this->getLastActivity()->changes()->toArray();
$this->assertSame($expectedChanges, $changes);
}
protected function createArticle(): Article
{
$article = new $this->article();

0 comments on commit a28239b

Please sign in to comment.
You can’t perform that action at this time.