1
0
mirror of https://github.com/Kovah/LinkAce.git synced 2025-04-21 23:42:10 +02:00

Refactor tag and list selection to correctly handle ids and names during bulk editing (#936)

This commit is contained in:
Kevin Woblick 2025-03-19 11:03:26 +01:00
parent 43828ec27a
commit fa7e8118a7
No known key found for this signature in database
GPG Key ID: 6A17121675A12D11
11 changed files with 169 additions and 74 deletions

View File

@ -37,7 +37,10 @@ class BulkEditController extends Controller
{
$models = explode(',', $request->input('models'));
$results = LinkRepository::bulkUpdate($models, $request->input());
$data = $request->input();
$data['tags'] = json_decode($data['tags']) ?? [];
$data['lists'] = json_decode($data['lists']) ?? [];
$results = LinkRepository::bulkUpdate($models, $data);
$successCount = $results->filter(fn($e) => $e !== null)->count();

View File

@ -5,7 +5,6 @@ namespace App\Http\Controllers\Models;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Traits\ChecksOrdering;
use App\Http\Controllers\Traits\ConfiguresLinkDisplay;
use App\Http\Controllers\Traits\HandlesQueryOrder;
use App\Http\Requests\Models\LinkStoreRequest;
use App\Http\Requests\Models\LinkUpdateRequest;
use App\Http\Requests\Models\ToggleLinkCheckRequest;
@ -71,7 +70,10 @@ class LinkController extends Controller
public function store(LinkStoreRequest $request): RedirectResponse
{
$link = LinkRepository::create($request->all(), true);
$data = $request->validated();
$data['tags'] = json_decode($data['tags']) ?? [];
$data['lists'] = json_decode($data['lists']) ?? [];
$link = LinkRepository::create($data, true);
flash(trans('link.added_successfully'), 'success');
@ -131,7 +133,10 @@ class LinkController extends Controller
public function update(LinkUpdateRequest $request, Link $link): RedirectResponse
{
$link = LinkRepository::update($link, $request->input());
$data = $request->validated();
$data['tags'] = json_decode($data['tags']) ?? [];
$data['lists'] = json_decode($data['lists']) ?? [];
$link = LinkRepository::update($link, $data);
flash(trans('link.updated_successfully'), 'success');
return redirect()->route('links.show', [$link->id]);

View File

@ -68,13 +68,11 @@ trait SearchesLinks
$search->where('status', '>', 1);
}
//dd($request->input('only_lists'), $request->input('only_tags'));
// Show by specific list only if applicable
if ($this->emptyLists = (bool)$request->input('empty_lists', false)) {
$search->doesntHave('lists');
} elseif ($request->input('only_lists')) {
$this->searchLists = array_map('intval', explode(',', $request->input('only_lists', '')));
$this->searchLists = json_decode($request->input('only_lists', '[]'));
$search->whereHas('lists', function ($query) {
$query->whereIn('id', $this->searchLists);
});
@ -84,7 +82,7 @@ trait SearchesLinks
if ($this->emptyTags = (bool)$request->input('empty_tags', false)) {
$search->doesntHave('tags');
} elseif ($request->input('only_tags')) {
$this->searchTags = array_map('intval', explode(',', $request->input('only_tags')));
$this->searchTags = json_decode($request->input('only_tags', '[]'));
$search->whereHas('tags', function ($query) {
$query->whereIn('id', $this->searchTags);
});

View File

@ -50,10 +50,10 @@ trait ProvidesTaxonomyOutput
$data = collect();
if ($old = old($taxonomy, false)) {
$items = explode(',', $old);
$items = json_decode($old);
foreach ($items as $item) {
if ((int)$item > 0) {
if (is_int($item) && $item > 0) {
$item = $model::find($item)?->load('user:id,name');
} else {
$item = [

View File

@ -76,12 +76,7 @@ class LinkRepository
'lists:id',
])->get();
$newTags = is_array($data['tags']) ? $data['tags'] : explode(',', $data['tags']);
$newTags = array_map('intval', array_filter($newTags));
$newLists = is_array($data['lists']) ? $data['lists'] : explode(',', $data['lists']);
$newLists = array_map('intval', array_filter($newLists));
return $links->map(function (Link $link) use ($data, $newTags, $newLists) {
return $links->map(function (Link $link) use ($data) {
if (!auth()->user()->can('update', $link)) {
Log::warning('Could not update ' . $link->id . ' during bulk update: Permission denied!');
return null;
@ -89,14 +84,14 @@ class LinkRepository
$linkData = $link->toArray();
$linkData['tags'] = $data['tags_mode'] === 'replace'
? $newTags
: array_merge($link->tags->pluck('id')->toArray(), $newTags);
? $data['tags']
: array_merge($link->tags->pluck('id')->toArray(), $data['tags']);
$linkData['lists'] = $data['lists_mode'] === 'replace'
? $newLists
: array_merge($link->lists->pluck('id')->toArray(), $newLists);
? $data['lists']
: array_merge($link->lists->pluck('id')->toArray(), $data['lists']);
$linkData['visibility'] = $data['visibility'] ?: $linkData['visibility'];
return LinkRepository::update($link, $linkData);
return self::update($link, $linkData);
});
}
@ -232,7 +227,7 @@ class LinkRepository
};
foreach ($entries as $entry) {
if ((int)$entry > 0) {
if (is_int($entry) && $entry > 0) {
$newEntry = $model::find($entry);
} else {
$newEntry = $model::firstOrCreate([

View File

@ -27,6 +27,7 @@ export default class TagsSelect {
delimiter: ',',
persist: false,
create: this.selectAllowsCreation(),
addPrecedence: true,
valueField: 'id',
labelField: 'name',
searchField: 'name',
@ -35,6 +36,18 @@ export default class TagsSelect {
this.setTextboxValue('');
this.refreshOptions();
},
onInitialize: function () {
if (!selectObject.$el.value.startsWith('[')) {
selectObject.$el.value = `[${selectObject.$el.value}]`;
}
},
onChange: function () {
const items = this.items.map((item) => {
const option = Object.values(this.options).find((option) => option.id === parseInt(item));
return option !== undefined ? option.id : item;
});
selectObject.$el.value = JSON.stringify(items.length > 0 ? items : []);
},
render: {
option: function (item, escape) {
return selectObject.renderItem(item, escape);

View File

@ -1,5 +1,7 @@
<?php
use App\Enums\ModelAttribute;
?>
@extends('layouts.app')
@ -92,17 +94,20 @@ use App\Enums\ModelAttribute;
<option value="{{ ModelAttribute::VISIBILITY_PUBLIC }}"
{{ (int)old('visibility', $query_settings['visibility']) === ModelAttribute::VISIBILITY_PUBLIC
? 'selected' : '' }}>
@lang('linkace.visibility'): @lang('attributes.visibility.' . ModelAttribute::VISIBILITY_PUBLIC)
@lang('linkace.visibility')
: @lang('attributes.visibility.' . ModelAttribute::VISIBILITY_PUBLIC)
</option>
<option value="{{ ModelAttribute::VISIBILITY_INTERNAL }}"
{{ (int)old('visibility', $query_settings['visibility']) === ModelAttribute::VISIBILITY_INTERNAL
? 'selected' : '' }}>
@lang('linkace.visibility'): @lang('attributes.visibility.' . ModelAttribute::VISIBILITY_INTERNAL)
@lang('linkace.visibility')
: @lang('attributes.visibility.' . ModelAttribute::VISIBILITY_INTERNAL)
</option>
<option value="{{ ModelAttribute::VISIBILITY_PRIVATE }}"
{{ (int)old('visibility', $query_settings['visibility']) === ModelAttribute::VISIBILITY_PRIVATE
? 'selected' : '' }}>
@lang('linkace.visibility'): @lang('attributes.visibility.' . ModelAttribute::VISIBILITY_PRIVATE)
@lang('linkace.visibility')
: @lang('attributes.visibility.' . ModelAttribute::VISIBILITY_PRIVATE)
</option>
</select>
</div>
@ -111,13 +116,15 @@ use App\Enums\ModelAttribute;
<div class="row mt-4">
<div class="col-md mb-3 mb-md-0">
@php ray($query_settings['only_lists'], json_encode($query_settings['only_lists'])) @endphp
<label for="only_lists" class="d-none" aria-hidden="true">
@lang('search.filter_by_list')
</label>
<input name="only_lists" id="only_lists" type="text" placeholder="@lang('search.filter_by_list')"
<input name="only_lists" id="only_lists" type="text"
placeholder="@lang('search.filter_by_list')"
class="tag-select" data-tag-data="{{ $all_lists->toJson() }}"
data-value="{{ json_encode($query_settings['only_lists']) }}"
data-allow-creation="1" data-tag-type="lists">
data-tag-type="lists">
</div>
<div class="col-md mb-3 mb-md-0">
@ -127,7 +134,7 @@ use App\Enums\ModelAttribute;
<input name="only_tags" id="only_tags" type="text" placeholder="@lang('search.filter_by_tag')"
class="tag-select" data-tag-data="{{ $all_tags->toJson() }}"
data-value="{{ json_encode($query_settings['only_tags']) }}"
data-allow-creation="1" data-tag-type="tags">
data-tag-type="tags">
</div>
<div class="col-md">
@ -143,7 +150,7 @@ use App\Enums\ModelAttribute;
</option>
@endforeach
<option value="random"
@if($query_settings['order_by'] == 'random') selected @endif>
@if($query_settings['order_by'] === 'random') selected @endif>
@lang('search.order_by.random')
</option>
</select>

View File

@ -40,22 +40,33 @@ class BulkEditApiTest extends TestCase
$this->patchJson('api/v2/bulk/links', [
'models' => [1, 2, 3, 4],
'tags' => [3],
'tags' => [3, 'new-tag'],
'tags_mode' => 'append',
'lists' => [3],
'lists' => [3, 'new list'],
'lists_mode' => 'append',
'visibility' => null,
])->assertJsonCount(4);
array_walk($links, fn ($link) => $link->refresh());
$this->assertEqualsCanonicalizing([1, 3], $links[0]->lists()->pluck('id')->toArray());
$this->assertEqualsCanonicalizing([1, 2, 3], $links[1]->lists()->pluck('id')->toArray());
$this->assertEqualsCanonicalizing([3], $links[2]->lists()->pluck('id')->toArray());
$this->assertDatabaseCount('tags', 4);
$this->assertDatabaseHas('tags', [
'id' => 4,
'name' => 'new-tag',
]);
$this->assertDatabaseCount('lists', 4);
$this->assertDatabaseHas('lists', [
'id' => 4,
'name' => 'new list',
]);
$this->assertEqualsCanonicalizing([1, 3], $links[0]->tags()->pluck('id')->toArray());
$this->assertEqualsCanonicalizing([1, 2, 3], $links[1]->tags()->pluck('id')->toArray());
$this->assertEqualsCanonicalizing([3], $links[2]->tags()->pluck('id')->toArray());
$this->assertEqualsCanonicalizing([1, 3, 4], $links[0]->lists()->pluck('id')->toArray());
$this->assertEqualsCanonicalizing([1, 2, 3, 4], $links[1]->lists()->pluck('id')->toArray());
$this->assertEqualsCanonicalizing([3, 4], $links[2]->lists()->pluck('id')->toArray());
$this->assertEqualsCanonicalizing([1, 3, 4], $links[0]->tags()->pluck('id')->toArray());
$this->assertEqualsCanonicalizing([1, 2, 3, 4], $links[1]->tags()->pluck('id')->toArray());
$this->assertEqualsCanonicalizing([3, 4], $links[2]->tags()->pluck('id')->toArray());
$this->assertEquals(ModelAttribute::VISIBILITY_PUBLIC, $links[0]->visibility);
$this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $links[1]->visibility);

View File

@ -2,6 +2,8 @@
namespace Tests\Controller\API;
use App\Models\LinkList;
use App\Models\Tag;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
@ -12,14 +14,12 @@ class BulkStoreApiTest extends TestCase
{
use RefreshDatabase;
private User $user;
protected function setUp(): void
{
parent::setUp();
$this->user = User::factory()->create();
$this->actingAs($this->user);
$user = User::factory()->create();
$this->actingAs($user);
Queue::fake();
}
@ -36,14 +36,17 @@ class BulkStoreApiTest extends TestCase
'duckduckgo.com' => Http::response($testHtml),
]);
$testList = LinkList::factory()->create();
$testTag = Tag::factory()->create();
$response = $this->postJson('api/v2/bulk/links', [
'models' => [
[
'url' => 'https://example.com',
'title' => 'The famous Example',
'description' => 'There could be a description here',
'lists' => [],
'tags' => [],
'lists' => [$testList->id, 'new List'],
'tags' => [$testTag->id, 'newTag'],
'visibility' => 1,
'check_disabled' => false,
],
@ -61,7 +64,11 @@ class BulkStoreApiTest extends TestCase
$response->assertSuccessful()->assertJsonIsArray();
$this->assertEquals('https://example.com', $response->json()[0]['url']);
$this->assertEquals($testList->name, $response->json()[0]['lists'][0]['name']);
$this->assertEquals('new List', $response->json()[0]['lists'][1]['name']);
$this->assertEquals('https://duckduckgo.com', $response->json()[1]['url']);
$this->assertEquals($testTag->name, $response->json()[0]['tags'][0]['name']);
$this->assertEquals('newTag', $response->json()[0]['tags'][1]['name']);
$this->assertDatabaseHas('links', [
'id' => 1,
@ -74,6 +81,18 @@ class BulkStoreApiTest extends TestCase
'url' => 'https://duckduckgo.com',
'title' => 'Search the Web',
]);
$this->assertDatabaseCount('lists', 2);
$this->assertDatabaseHas('lists', [
'id' => 2,
'name' => 'new List',
]);
$this->assertDatabaseCount('tags', 2);
$this->assertDatabaseHas('tags', [
'id' => 2,
'name' => 'newTag',
]);
}
public function test_store_lists(): void

View File

@ -48,24 +48,44 @@ class BulkEditControllerTest extends TestCase
$this->post('bulk-edit/update-links', [
'models' => '1,2,3,4',
'tags' => '3',
'tags' => json_encode([3, 'new-tag', '1337']),
'tags_mode' => 'append',
'lists' => '3',
'lists' => json_encode([3, 'new list', '1001']),
'lists_mode' => 'append',
'visibility' => null,
])
->assertRedirect('links')
->assertSessionHas('flash_notification.0.message', 'Successfully updated 3 Links out of 4 selected ones.');
array_walk($links, fn ($link) => $link->refresh());
array_walk($links, fn($link) => $link->refresh());
$this->assertEqualsCanonicalizing([3, 1], $links[0]->lists()->pluck('id')->toArray());
$this->assertEqualsCanonicalizing([1, 2, 3], $links[1]->lists()->pluck('id')->toArray());
$this->assertEqualsCanonicalizing([3], $links[2]->lists()->pluck('id')->toArray());
$this->assertDatabaseCount('tags', 5);
$this->assertDatabaseHas('tags', [
'id' => 4,
'name' => 'new-tag',
]);
$this->assertDatabaseHas('tags', [
'id' => 5,
'name' => '1337',
]);
$this->assertEqualsCanonicalizing([3, 1], $links[0]->tags()->pluck('id')->toArray());
$this->assertEqualsCanonicalizing([1, 2, 3], $links[1]->tags()->pluck('id')->toArray());
$this->assertEqualsCanonicalizing([3], $links[2]->tags()->pluck('id')->toArray());
$this->assertDatabaseCount('lists', 5);
$this->assertDatabaseHas('lists', [
'id' => 4,
'name' => 'new list',
]);
$this->assertDatabaseHas('lists', [
'id' => 5,
'name' => '1001',
]);
$this->assertEqualsCanonicalizing([3, 1, 4, 5], $links[0]->lists()->pluck('id')->toArray());
$this->assertEqualsCanonicalizing([1, 2, 3, 4, 5], $links[1]->lists()->pluck('id')->toArray());
$this->assertEqualsCanonicalizing([3, 4, 5], $links[2]->lists()->pluck('id')->toArray());
$this->assertEqualsCanonicalizing([3, 1, 4, 5], $links[0]->tags()->pluck('id')->toArray());
$this->assertEqualsCanonicalizing([1, 2, 3, 4, 5], $links[1]->tags()->pluck('id')->toArray());
$this->assertEqualsCanonicalizing([3, 4, 5], $links[2]->tags()->pluck('id')->toArray());
$this->assertEquals(ModelAttribute::VISIBILITY_PUBLIC, $links[0]->visibility);
$this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $links[1]->visibility);
@ -88,7 +108,7 @@ class BulkEditControllerTest extends TestCase
->assertRedirect('links')
->assertSessionHas('flash_notification.0.message', 'Successfully updated 3 Links out of 4 selected ones.');
array_walk($links, fn ($link) => $link->refresh());
array_walk($links, fn($link) => $link->refresh());
$this->assertEqualsCanonicalizing([1], $links[0]->lists()->pluck('id')->toArray());
$this->assertEqualsCanonicalizing([1, 2], $links[1]->lists()->pluck('id')->toArray());
@ -109,16 +129,16 @@ class BulkEditControllerTest extends TestCase
$this->post('bulk-edit/update-links', [
'models' => '1,2,3,4',
'tags' => '2,3',
'tags' => json_encode([2, 3]),
'tags_mode' => 'replace',
'lists' => '3',
'lists' => json_encode([3]),
'lists_mode' => 'replace',
'visibility' => ModelAttribute::VISIBILITY_INTERNAL,
])
->assertRedirect('links')
->assertSessionHas('flash_notification.0.message', 'Successfully updated 3 Links out of 4 selected ones.');
array_walk($links, fn ($link) => $link->refresh());
array_walk($links, fn($link) => $link->refresh());
$this->assertEqualsCanonicalizing([3], $links[0]->lists()->pluck('id')->sort()->toArray());
$this->assertEqualsCanonicalizing([3], $links[1]->lists()->pluck('id')->sort()->toArray());
@ -149,7 +169,7 @@ class BulkEditControllerTest extends TestCase
->assertRedirect('lists')
->assertSessionHas('flash_notification.0.message', 'Successfully updated 3 Lists out of 4 selected ones.');
array_walk($lists, fn ($list) => $list->refresh());
array_walk($lists, fn($list) => $list->refresh());
$this->assertEquals(ModelAttribute::VISIBILITY_PUBLIC, $lists[0]->visibility);
$this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $lists[1]->visibility);
@ -172,7 +192,7 @@ class BulkEditControllerTest extends TestCase
->assertRedirect('lists')
->assertSessionHas('flash_notification.0.message', 'Successfully updated 3 Lists out of 4 selected ones.');
array_walk($lists, fn ($list) => $list->refresh());
array_walk($lists, fn($list) => $list->refresh());
$this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $lists[0]->visibility);
$this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $lists[1]->visibility);
@ -195,7 +215,7 @@ class BulkEditControllerTest extends TestCase
->assertRedirect('tags')
->assertSessionHas('flash_notification.0.message', 'Successfully updated 3 Tags out of 4 selected ones.');
array_walk($tags, fn ($tag) => $tag->refresh());
array_walk($tags, fn($tag) => $tag->refresh());
$this->assertEquals(ModelAttribute::VISIBILITY_PUBLIC, $tags[0]->visibility);
$this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $tags[1]->visibility);
@ -218,7 +238,7 @@ class BulkEditControllerTest extends TestCase
->assertRedirect('tags')
->assertSessionHas('flash_notification.0.message', 'Successfully updated 3 Tags out of 4 selected ones.');
array_walk($tags, fn ($tag) => $tag->refresh());
array_walk($tags, fn($tag) => $tag->refresh());
$this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $tags[0]->visibility);
$this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $tags[1]->visibility);
@ -243,9 +263,12 @@ class BulkEditControllerTest extends TestCase
'type' => 'links',
])
->assertRedirect('links')
->assertSessionHas('flash_notification.0.message', 'Successfully moved 2 Links out of 3 selected ones to the trash.');
->assertSessionHas(
'flash_notification.0.message',
'Successfully moved 2 Links out of 3 selected ones to the trash.'
);
array_walk($links, fn ($link) => $link->refresh());
array_walk($links, fn($link) => $link->refresh());
$this->assertNotNull($links[0]->deleted_at);
$this->assertNotNull($links[1]->deleted_at);
$this->assertNull($otherLink->deleted_at);
@ -255,9 +278,12 @@ class BulkEditControllerTest extends TestCase
'type' => 'lists',
])
->assertRedirect('lists')
->assertSessionHas('flash_notification.1.message', 'Successfully moved 2 Lists out of 3 selected ones to the trash.');
->assertSessionHas(
'flash_notification.1.message',
'Successfully moved 2 Lists out of 3 selected ones to the trash.'
);
array_walk($lists, fn ($list) => $list->refresh());
array_walk($lists, fn($list) => $list->refresh());
$this->assertNotNull($lists[0]->deleted_at);
$this->assertNotNull($lists[1]->deleted_at);
$this->assertNull($otherList->deleted_at);
@ -267,9 +293,12 @@ class BulkEditControllerTest extends TestCase
'type' => 'tags',
])
->assertRedirect('tags')
->assertSessionHas('flash_notification.2.message', 'Successfully moved 2 Tags out of 3 selected ones to the trash.');
->assertSessionHas(
'flash_notification.2.message',
'Successfully moved 2 Tags out of 3 selected ones to the trash.'
);
array_walk($tags, fn ($tag) => $tag->refresh());
array_walk($tags, fn($tag) => $tag->refresh());
$this->assertNotNull($tags[0]->deleted_at);
$this->assertNotNull($tags[1]->deleted_at);
$this->assertNull($otherTag->deleted_at);

View File

@ -87,25 +87,36 @@ class LinkControllerTest extends TestCase
public function test_full_store_request(): void
{
$tag = Tag::factory()->create();
$list = LinkList::factory()->create();
$tag = Tag::factory()->create(['name' => 'testTag']);
$list = LinkList::factory()->create(['name' => 'Test List']);
$this->post('links', [
'url' => 'https://example.com',
'title' => 'My custom title',
'description' => 'My custom description',
'lists' => $list->name,
'tags' => $tag->name,
'lists' => json_encode([$list->id, 'new list']),
'tags' => json_encode([$tag->id, 'new-tag']),
'visibility' => 1,
])->assertRedirect('links/1');
$this->assertDatabaseCount('tags', 4);
$this->assertDatabaseHas('tags', [
'id' => 4,
'name' => 'new-tag',
]);
$this->assertDatabaseCount('lists', 4);
$this->assertDatabaseHas('lists', [
'id' => 4,
'name' => 'new list',
]);
$databaseLink = Link::first();
$this->assertEquals('https://example.com', $databaseLink->url);
$this->assertEquals('My custom title', $databaseLink->title);
$this->assertEquals('My custom description', $databaseLink->description);
$this->assertEquals($list->name, $databaseLink->lists->first()->name);
$this->assertEquals($tag->name, $databaseLink->tags->first()->name);
$this->assertEqualsCanonicalizing(['Test List', 'new list'], $databaseLink->lists->pluck('name')->toArray());
$this->assertEqualsCanonicalizing(['testTag', 'new-tag'], $databaseLink->tags->pluck('name')->toArray());
}
public function test_store_request_with_duplicate(): void
@ -309,13 +320,15 @@ class LinkControllerTest extends TestCase
public function test_update_response(): void
{
$this->createTestLinks();
$this->createTestLists();
$this->createTestTags();
$this->patch('links/1', [
'url' => 'https://new-public-link.com',
'title' => 'New Title',
'description' => 'New Description',
'lists' => null,
'tags' => null,
'lists' => json_encode([1, 'new list']),
'tags' => json_encode([1, 'new-tag']),
'visibility' => 1,
'check_disabled' => '0',
])->assertRedirect('links/1');
@ -326,6 +339,8 @@ class LinkControllerTest extends TestCase
$this->assertEquals('https://new-public-link.com', $link->url);
$this->assertEquals('New Title', $link->title);
$this->assertEquals('New Description', $link->description);
$this->assertEqualsCanonicalizing(['Public List', 'new list'], $link->lists->pluck('name')->toArray());
$this->assertEqualsCanonicalizing(['Public Tag', 'new-tag'], $link->tags->pluck('name')->toArray());
$historyData = $link->audits()->first()->getModified();