From 7a79fd18f08a69f96ae5fbe74a6be3731ce7746b Mon Sep 17 00:00:00 2001 From: Marcus Moore Date: Tue, 20 May 2025 11:40:44 -0700 Subject: [PATCH 01/12] Bind the selected webhook and avoid clearing from database --- app/Livewire/SlackSettingsForm.php | 1 - .../livewire/slack-settings-form.blade.php | 20 +------------------ 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/app/Livewire/SlackSettingsForm.php b/app/Livewire/SlackSettingsForm.php index 8bc775a27a..694de89a82 100644 --- a/app/Livewire/SlackSettingsForm.php +++ b/app/Livewire/SlackSettingsForm.php @@ -191,7 +191,6 @@ class SlackSettingsForm extends Component $this->setting->webhook_endpoint = ''; $this->setting->webhook_channel = ''; $this->setting->webhook_botname = ''; - $this->setting->webhook_selected = ''; $this->setting->save(); diff --git a/resources/views/livewire/slack-settings-form.blade.php b/resources/views/livewire/slack-settings-form.blade.php index 849dfabdfa..abb776bcea 100644 --- a/resources/views/livewire/slack-settings-form.blade.php +++ b/resources/views/livewire/slack-settings-form.blade.php @@ -72,6 +72,7 @@ :options="['slack' => trans('admin/settings/general.slack'), 'general' => trans('admin/settings/general.general_webhook'),'google' => trans('admin/settings/general.google_workspaces'), 'microsoft' => trans('admin/settings/general.ms_teams')]" :selected="old('webhook_selected', $webhook_selected)" :disabled="Helper::isDemoMode()" + :for-livewire="true" data-minimum-results-for-search="-1" class="form-control" style="width:100%" @@ -174,22 +175,3 @@ - - - - -@section('moar_scripts') - -@endsection - - From d6b69c8cc22f1c0c22c3aa5a1c6b1089fd065e35 Mon Sep 17 00:00:00 2001 From: Marcus Moore Date: Tue, 20 May 2025 12:00:58 -0700 Subject: [PATCH 02/12] Fix webhook selected value in mount method --- app/Livewire/SlackSettingsForm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Livewire/SlackSettingsForm.php b/app/Livewire/SlackSettingsForm.php index 694de89a82..535a83413f 100644 --- a/app/Livewire/SlackSettingsForm.php +++ b/app/Livewire/SlackSettingsForm.php @@ -71,7 +71,7 @@ class SlackSettingsForm extends Component $this->setting = Setting::getSettings(); $this->save_button = trans('general.save'); - $this->webhook_selected = $this->setting->webhook_selected ?? 'slack'; + $this->webhook_selected = ($this->setting->webhook_selected !== '') ? $this->setting->webhook_selected : 'slack'; $this->webhook_name = $this->webhook_text[$this->setting->webhook_selected]["name"] ?? $this->webhook_text['slack']["name"]; $this->webhook_icon = $this->webhook_text[$this->setting->webhook_selected]["icon"] ?? $this->webhook_text['slack']["icon"]; $this->webhook_placeholder = $this->webhook_text[$this->setting->webhook_selected]["placeholder"] ?? $this->webhook_text['slack']["placeholder"]; From 13956254ce2fc23ad3ad0a139bef63132049024a Mon Sep 17 00:00:00 2001 From: Marcus Moore Date: Tue, 20 May 2025 12:27:19 -0700 Subject: [PATCH 03/12] Add migration to fix webhook settings --- ...17_repopulate_webhook_selected_setting.php | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 database/migrations/2025_05_20_190317_repopulate_webhook_selected_setting.php diff --git a/database/migrations/2025_05_20_190317_repopulate_webhook_selected_setting.php b/database/migrations/2025_05_20_190317_repopulate_webhook_selected_setting.php new file mode 100644 index 0000000000..e552bbe113 --- /dev/null +++ b/database/migrations/2025_05_20_190317_repopulate_webhook_selected_setting.php @@ -0,0 +1,39 @@ +first(); + + /** If webhook settings were cleared via the integration settings page, + * the webhook_selected was cleared as well when it should have reset to "slack". + */ + if ( + empty($settings->webhook_selected) && + (empty($settings->webhook_botname) && empty($settings->webhook_channel) && empty($settings->webhook_endpoint)) + ) { + DB::table('settings')->update(['webhook_selected' => 'slack']); + } + + /** If webhook settings were cleared via the integration settings page, + * then slack settings were re-added; then webhook_selected was not being set to "slack" as needed. + */ + if (str_contains($settings->webhook_endpoint, 'slack.com')) { + DB::table('settings')->update(['webhook_selected' => 'slack']); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // + } +}; From 5739393f8d0339e9783e85c8c6832d33236c4756 Mon Sep 17 00:00:00 2001 From: Marcus Moore Date: Tue, 20 May 2025 12:45:23 -0700 Subject: [PATCH 04/12] Check to make sure settings exist before attempting to update them --- ...17_repopulate_webhook_selected_setting.php | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/database/migrations/2025_05_20_190317_repopulate_webhook_selected_setting.php b/database/migrations/2025_05_20_190317_repopulate_webhook_selected_setting.php index e552bbe113..be14ea04ea 100644 --- a/database/migrations/2025_05_20_190317_repopulate_webhook_selected_setting.php +++ b/database/migrations/2025_05_20_190317_repopulate_webhook_selected_setting.php @@ -11,21 +11,23 @@ return new class extends Migration { { $settings = DB::table('settings')->first(); - /** If webhook settings were cleared via the integration settings page, - * the webhook_selected was cleared as well when it should have reset to "slack". - */ - if ( - empty($settings->webhook_selected) && - (empty($settings->webhook_botname) && empty($settings->webhook_channel) && empty($settings->webhook_endpoint)) - ) { - DB::table('settings')->update(['webhook_selected' => 'slack']); - } + if ($settings) { + /** If webhook settings were cleared via the integration settings page, + * the webhook_selected was cleared as well when it should have reset to "slack". + */ + if ( + empty($settings->webhook_selected) && + (empty($settings->webhook_botname) && empty($settings->webhook_channel) && empty($settings->webhook_endpoint)) + ) { + DB::table('settings')->update(['webhook_selected' => 'slack']); + } - /** If webhook settings were cleared via the integration settings page, - * then slack settings were re-added; then webhook_selected was not being set to "slack" as needed. - */ - if (str_contains($settings->webhook_endpoint, 'slack.com')) { - DB::table('settings')->update(['webhook_selected' => 'slack']); + /** If webhook settings were cleared via the integration settings page, + * then slack settings were re-added; then webhook_selected was not being set to "slack" as needed. + */ + if (str_contains($settings->webhook_endpoint, 'slack.com')) { + DB::table('settings')->update(['webhook_selected' => 'slack']); + } } } From 5f883310b5cd62565a944e414cf8dcbcc10aeea6 Mon Sep 17 00:00:00 2001 From: Marcus Moore Date: Wed, 21 May 2025 16:48:26 -0700 Subject: [PATCH 05/12] Improve display of user on accessory show page --- .../Accessories/AccessoriesController.php | 5 ++++- resources/views/accessories/view.blade.php | 6 +----- resources/views/blade/full-user-name.blade.php | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 resources/views/blade/full-user-name.blade.php diff --git a/app/Http/Controllers/Accessories/AccessoriesController.php b/app/Http/Controllers/Accessories/AccessoriesController.php index bc1ac56fc9..66a914e9a9 100755 --- a/app/Http/Controllers/Accessories/AccessoriesController.php +++ b/app/Http/Controllers/Accessories/AccessoriesController.php @@ -220,7 +220,10 @@ class AccessoriesController extends Controller */ public function show(Accessory $accessory) : View | RedirectResponse { - $accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessory->id); + $accessory->loadCount('checkouts as checkouts_count'); + + $accessory->load(['adminuser' => fn($query) => $query->withTrashed()]); + $this->authorize('view', $accessory); return view('accessories.view', compact('accessory')); } diff --git a/resources/views/accessories/view.blade.php b/resources/views/accessories/view.blade.php index 137f64fef3..0a9ebdfbd1 100644 --- a/resources/views/accessories/view.blade.php +++ b/resources/views/accessories/view.blade.php @@ -324,11 +324,7 @@
- @if ($accessory->adminuser) - {{ $accessory->adminuser->present()->fullName() }} - @else - {{ trans('admin/reports/general.deleted_user') }} - @endif +
diff --git a/resources/views/blade/full-user-name.blade.php b/resources/views/blade/full-user-name.blade.php new file mode 100644 index 0000000000..af61b766a7 --- /dev/null +++ b/resources/views/blade/full-user-name.blade.php @@ -0,0 +1,16 @@ +@props([ + 'user' +]) + +@if ($user) + @if (! $user->trashed()) + {{-- if the user is in database but soft-deleted --}} + {{ $user->present()->fullName() }} + @else + {{-- if the user exists --}} + {{ $user->present()->fullName() }} + @endif +@else + {{-- if the user does not exist --}} + Unknown User +@endif From ce18b91b03096e4511202443fc3e58c0c4c4cb2d Mon Sep 17 00:00:00 2001 From: Marcus Moore Date: Wed, 21 May 2025 16:49:15 -0700 Subject: [PATCH 06/12] Add translation --- resources/lang/en-US/general.php | 1 + resources/views/blade/full-user-name.blade.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/lang/en-US/general.php b/resources/lang/en-US/general.php index d2e16aafbb..ae3c4e54ad 100644 --- a/resources/lang/en-US/general.php +++ b/resources/lang/en-US/general.php @@ -303,6 +303,7 @@ return [ 'type' => 'Type', 'undeployable' => 'Un-deployable', 'unknown_admin' => 'Unknown Admin', + 'unknown_user' => 'Unknown User', 'username' => 'Username', 'update' => 'Update', 'updating_item' => 'Updating :item', diff --git a/resources/views/blade/full-user-name.blade.php b/resources/views/blade/full-user-name.blade.php index af61b766a7..2cb3f8c6a6 100644 --- a/resources/views/blade/full-user-name.blade.php +++ b/resources/views/blade/full-user-name.blade.php @@ -12,5 +12,5 @@ @endif @else {{-- if the user does not exist --}} - Unknown User + {{ trans('general.unknown_user') }} @endif From 932b589bd9f348e2a288b3770d37469c8a2dc5dc Mon Sep 17 00:00:00 2001 From: Marcus Moore Date: Wed, 21 May 2025 17:28:41 -0700 Subject: [PATCH 07/12] Do not show section if user doesn't exist --- resources/lang/en-US/general.php | 1 - resources/views/accessories/view.blade.php | 20 ++++++++++--------- .../views/blade/full-user-name.blade.php | 3 --- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/resources/lang/en-US/general.php b/resources/lang/en-US/general.php index ae3c4e54ad..d2e16aafbb 100644 --- a/resources/lang/en-US/general.php +++ b/resources/lang/en-US/general.php @@ -303,7 +303,6 @@ return [ 'type' => 'Type', 'undeployable' => 'Un-deployable', 'unknown_admin' => 'Unknown Admin', - 'unknown_user' => 'Unknown User', 'username' => 'Username', 'update' => 'Update', 'updating_item' => 'Updating :item', diff --git a/resources/views/accessories/view.blade.php b/resources/views/accessories/view.blade.php index 0a9ebdfbd1..9a7a402cb0 100644 --- a/resources/views/accessories/view.blade.php +++ b/resources/views/accessories/view.blade.php @@ -317,16 +317,18 @@ @endif -
-
- - {{ trans('general.created_by') }} - + @if ($accessory->adminuser) +
+
+ + {{ trans('general.created_by') }} + +
+
+ +
-
- -
-
+ @endif
diff --git a/resources/views/blade/full-user-name.blade.php b/resources/views/blade/full-user-name.blade.php index 2cb3f8c6a6..7e3b9b11c1 100644 --- a/resources/views/blade/full-user-name.blade.php +++ b/resources/views/blade/full-user-name.blade.php @@ -10,7 +10,4 @@ {{-- if the user exists --}} {{ $user->present()->fullName() }} @endif -@else - {{-- if the user does not exist --}} - {{ trans('general.unknown_user') }} @endif From 8c90efe7452799f0fcde93e4c259d5bb0efc5ec0 Mon Sep 17 00:00:00 2001 From: Marcus Moore Date: Thu, 22 May 2025 15:02:14 -0700 Subject: [PATCH 08/12] Add authorization check --- .../views/blade/full-user-name.blade.php | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/resources/views/blade/full-user-name.blade.php b/resources/views/blade/full-user-name.blade.php index 7e3b9b11c1..ad5e89addf 100644 --- a/resources/views/blade/full-user-name.blade.php +++ b/resources/views/blade/full-user-name.blade.php @@ -2,12 +2,22 @@ 'user' ]) -@if ($user) - @if (! $user->trashed()) - {{-- if the user is in database but soft-deleted --}} - {{ $user->present()->fullName() }} +@if($user) + @can('view', $user) + @if(! $user->trashed()) + {{-- if the user is in database but soft-deleted --}} + {{ $user->present()->fullName() }} + @else + {{-- if the user exists --}} + {{ $user->present()->fullName() }} + @endif @else - {{-- if the user exists --}} - {{ $user->present()->fullName() }} - @endif + @if(! $user->trashed()) + {{-- if the user is in database but soft-deleted --}} + {{ $user->present()->fullName() }} + @else + {{-- if the user exists --}} + {{ $user->present()->fullName() }} + @endif + @endcan @endif From 7cc216eb38e0356ae73dc650b9ecc9194c090882 Mon Sep 17 00:00:00 2001 From: Marcus Moore Date: Thu, 22 May 2025 15:05:24 -0700 Subject: [PATCH 09/12] Extract variable --- resources/views/blade/full-user-name.blade.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/resources/views/blade/full-user-name.blade.php b/resources/views/blade/full-user-name.blade.php index ad5e89addf..2e072236a7 100644 --- a/resources/views/blade/full-user-name.blade.php +++ b/resources/views/blade/full-user-name.blade.php @@ -3,21 +3,25 @@ ]) @if($user) + @php + $fullName = $user->present()->fullName(); + @endphp + @can('view', $user) @if(! $user->trashed()) {{-- if the user is in database but soft-deleted --}} - {{ $user->present()->fullName() }} + {{ $fullName }} @else {{-- if the user exists --}} - {{ $user->present()->fullName() }} + {{ $fullName }} @endif @else @if(! $user->trashed()) {{-- if the user is in database but soft-deleted --}} - {{ $user->present()->fullName() }} + {{ $fullName }} @else {{-- if the user exists --}} - {{ $user->present()->fullName() }} + {{ $fullName }} @endif @endcan @endif From be60370eae4f1b964cca8e676e85c5739c45e7d6 Mon Sep 17 00:00:00 2001 From: Marcus Moore Date: Thu, 22 May 2025 16:33:45 -0700 Subject: [PATCH 10/12] Add test --- .../Unit/BladeComponents/UserFullNameTest.php | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/Unit/BladeComponents/UserFullNameTest.php diff --git a/tests/Unit/BladeComponents/UserFullNameTest.php b/tests/Unit/BladeComponents/UserFullNameTest.php new file mode 100644 index 0000000000..8c36cb4ffe --- /dev/null +++ b/tests/Unit/BladeComponents/UserFullNameTest.php @@ -0,0 +1,22 @@ +actingAs(User::factory()->viewUsers()->create()); + + $user = User::factory()->create(['first_name' => 'Jim', 'last_name' => 'Bagg']); + + $renderedTemplateString = View::make('blade.full-user-name', ['user' => $user])->render(); + + $this->assertStringContainsString('assertStringContainsString('Jim Bagg', $renderedTemplateString); + } +} From bea426d8e602fab64fb90d03700e07d43f0be849 Mon Sep 17 00:00:00 2001 From: Marcus Moore Date: Thu, 22 May 2025 16:54:52 -0700 Subject: [PATCH 11/12] Add test --- .../Unit/BladeComponents/UserFullNameTest.php | 79 +++++++++++++++++-- 1 file changed, 74 insertions(+), 5 deletions(-) diff --git a/tests/Unit/BladeComponents/UserFullNameTest.php b/tests/Unit/BladeComponents/UserFullNameTest.php index 8c36cb4ffe..bbd74c7fa1 100644 --- a/tests/Unit/BladeComponents/UserFullNameTest.php +++ b/tests/Unit/BladeComponents/UserFullNameTest.php @@ -3,20 +3,89 @@ namespace Tests\Unit\BladeComponents; use App\Models\User; +use Generator; use Illuminate\Support\Facades\View; +use PHPUnit\Framework\Assert; +use PHPUnit\Framework\Attributes\DataProvider; use Tests\TestCase; class UserFullNameTest extends TestCase { - public function testComponent() + public static function provider(): Generator { - $this->actingAs(User::factory()->viewUsers()->create()); + yield 'Renders link to user if they exist and the authenticated user can view them' => [ + function () { + return [ + 'actor' => User::factory()->viewUsers()->create(), + 'user' => User::factory()->create(['first_name' => 'Jim', 'last_name' => 'Bagg']), + 'assertions' => function ($rendered) { + Assert::assertStringContainsString('create(['first_name' => 'Jim', 'last_name' => 'Bagg']); + yield 'Renders struck-through link to user if they are deleted and the authenticated user can view them' => [ + function () { + return [ + 'actor' => User::factory()->viewUsers()->create(), + 'user' => User::factory()->deleted()->create(['first_name' => 'Jim', 'last_name' => 'Bagg']), + 'assertions' => function ($rendered) { + Assert::assertStringContainsString(' [ + function () { + return [ + 'actor' => User::factory()->create(), + 'user' => User::factory()->create(['first_name' => 'Jim', 'last_name' => 'Bagg']), + 'assertions' => function ($rendered) { + Assert::assertStringContainsString('Jim Bagg', $rendered); + Assert::assertStringNotContainsString(' [ + function () { + return [ + 'actor' => User::factory()->create(), + 'user' => User::factory()->deleted()->create(['first_name' => 'Jim', 'last_name' => 'Bagg']), + 'assertions' => function ($rendered) { + Assert::assertStringContainsString('Jim Bagg', $rendered); + }, + ]; + } + ]; + + yield 'Renders nothing if the provided user is null' => [ + function () { + return [ + 'actor' => User::factory()->create(), + 'user' => null, + 'assertions' => function ($rendered) { + Assert::assertEmpty($rendered); + }, + ]; + } + ]; + } + + #[DataProvider('provider')] + public function testComponent($provided) + { + ['actor' => $actor, 'user' => $user, 'assertions' => $assertions] = $provided(); + + $this->actingAs($actor); $renderedTemplateString = View::make('blade.full-user-name', ['user' => $user])->render(); - $this->assertStringContainsString('assertStringContainsString('Jim Bagg', $renderedTemplateString); + $assertions($renderedTemplateString); } } From 4b95790e2ff2ed522a9f2cc3490833464d5f24e0 Mon Sep 17 00:00:00 2001 From: Brady Wetherington Date: Wed, 14 May 2025 14:32:50 +0200 Subject: [PATCH 12/12] WIP: new tests for checkin/checkout counters note that the test isnt going to work More WIP for checkout counters Got all tests passing --- .../Assets/Api/CheckinCheckoutCounters.php | 81 +++++++++++++++++++ .../Assets/Ui/CheckinCheckoutCounters.php | 65 +++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 tests/Feature/Assets/Api/CheckinCheckoutCounters.php create mode 100644 tests/Feature/Assets/Ui/CheckinCheckoutCounters.php diff --git a/tests/Feature/Assets/Api/CheckinCheckoutCounters.php b/tests/Feature/Assets/Api/CheckinCheckoutCounters.php new file mode 100644 index 0000000000..82309586e0 --- /dev/null +++ b/tests/Feature/Assets/Api/CheckinCheckoutCounters.php @@ -0,0 +1,81 @@ +superuser()->create(); + + //make a user + $user = User::factory()->create(); + + //need a model for the asset + $model = AssetModel::factory()->create(); + + //need a status for the asset, too + $status = Statuslabel::factory()->readyToDeploy()->create(); + + + //make an asset using the API (this is for the API after all!) + $response = $this->actingAsForApi($admin) + ->postJson(route('api.assets.store'), [ + 'asset_tag' => 'random_string', + 'model_id' => $model->id, + 'status_id' => $status->id, + ])->assertOk() + ->assertStatusMessageIs('success') + ->json(); + \Log::error(print_r($response, true)); + + //check the counters + $asset = Asset::find($response['payload']['id']); + $this->assertEquals(0, $asset->checkin_counter); + $this->assertEquals(0, $asset->checkout_counter); + + //do a checkout + $this->actingAsForApi($admin) + ->postJson(route('api.asset.checkout', $asset), [ + 'checkout_to_type' => 'user', + 'assigned_user' => $user->id, + 'checkout_at' => '2024-04-01', + 'expected_checkin' => '2024-04-08', + 'name' => 'Changed Name', + 'note' => 'Here is a cool note!', + ]) + ->assertOk(); + + $asset->refresh(); + //check the counters. both. + $this->assertEquals(0, $asset->checkin_counter); + $this->assertEquals(1, $asset->checkout_counter); //why does _this_ fail?! + + //do a checkin + $this->actingAsForApi(User::factory()->checkinAssets()->create()) + ->postJson(route('api.asset.checkin', $asset), [ + 'name' => 'Changed Name', + 'status_id' => $status->id, + ]) + ->assertOk(); + + //check the counters, again. + $asset->refresh(); + $this->assertEquals(1, $asset->checkin_counter); //wait, _this_ fails too?! WTH? + $this->assertEquals(1, $asset->checkout_counter); //okay, _nothing_ works. Now I'm confused. + } +} diff --git a/tests/Feature/Assets/Ui/CheckinCheckoutCounters.php b/tests/Feature/Assets/Ui/CheckinCheckoutCounters.php new file mode 100644 index 0000000000..cd4faeaad9 --- /dev/null +++ b/tests/Feature/Assets/Ui/CheckinCheckoutCounters.php @@ -0,0 +1,65 @@ +admin()->create(); + $user = User::factory()->create(); + + // create an asset using the GUI + $this->actingAs($admin) + ->post(route('hardware.store'), [ + 'asset_tags' => ['1' => '1234'], + 'model_id' => AssetModel::factory()->create()->id, + 'status_id' => Statuslabel::factory()->readyToDeploy()->create()->id, + ])->assertRedirect(); + + $asset = Asset::where('asset_tag', '1234')->sole(); + + //ensure counters are initialized properly + $this->assertEquals(0,$asset->checkout_counter); + $this->assertEquals(0,$asset->checkin_counter); + + //perform a checkout + $this->actingAs($admin) + ->post(route('hardware.checkout.store', $asset), [ + 'checkout_to_type' => 'user', + // overwrite the value from the default fields set above + 'assigned_user' => (string) $user->id, + 'name' => 'Changed Name', + 'checkout_at' => '2024-03-18', + 'expected_checkin' => '2024-03-28', + 'note' => 'An awesome note', + ])->assertRedirect()->assertSessionHasNoErrors(); + + $asset->refresh(); +// dump($asset); + $this->assertEquals(1,$asset->checkout_counter); + $this->assertEquals(0,$asset->checkin_counter); + + //perform a check-in + $this->actingAs($admin) + ->post( + route('hardware.checkin.store', [$asset]), + [ + 'name' => 'Changed Name Again', + ], + )->assertRedirect()->assertSessionHasNoErrors(); + + $asset->refresh(); +// dump($asset); + $this->assertEquals(1,$asset->checkout_counter); + $this->assertEquals(1,$asset->checkin_counter); + } +} \ No newline at end of file