Merge branch 'develop' into fix_unclickable_nav_bar_and_notifcation_block

This commit is contained in:
snipe 2023-05-24 15:56:01 -07:00 committed by GitHub
commit 9086cb5310
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 124 additions and 22 deletions

View file

@ -947,8 +947,10 @@ class Asset extends Depreciable
->orWhere('assets_users.first_name', 'LIKE', '%'.$term.'%')
->orWhere('assets_users.last_name', 'LIKE', '%'.$term.'%')
->orWhere('assets_users.username', 'LIKE', '%'.$term.'%')
->orWhereRaw('CONCAT('.DB::getTablePrefix().'assets_users.first_name," ",'.DB::getTablePrefix().'assets_users.last_name) LIKE ?', ["%$term%"]);
->orWhereMultipleColumns([
'assets_users.first_name',
'assets_users.last_name',
], $term);
}
/**
@ -1343,7 +1345,10 @@ class Asset extends Depreciable
})->orWhere(function ($query) use ($search) {
$query->where('assets_users.first_name', 'LIKE', '%'.$search.'%')
->orWhere('assets_users.last_name', 'LIKE', '%'.$search.'%')
->orWhereRaw('CONCAT('.DB::getTablePrefix().'assets_users.first_name," ",'.DB::getTablePrefix().'assets_users.last_name) LIKE ?', ["%$search%"])
->orWhereMultipleColumns([
'assets_users.first_name',
'assets_users.last_name',
], $search)
->orWhere('assets_users.username', 'LIKE', '%'.$search.'%')
->orWhere('assets_locations.name', 'LIKE', '%'.$search.'%')
->orWhere('assigned_assets.name', 'LIKE', '%'.$search.'%');

View file

@ -5,6 +5,7 @@ namespace App\Models\Traits;
use App\Models\Asset;
use App\Models\CustomField;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
/**
* This trait allows for cleaner searching of models,
@ -164,7 +165,13 @@ trait Searchable
}
// I put this here because I only want to add the concat one time in the end of the user relation search
if($relation == 'user') {
$query->orWhereRaw('CONCAT (users.first_name, " ", users.last_name) LIKE ?', ["%{$term}%"]);
$query->orWhereRaw(
$this->buildMultipleColumnSearch([
'users.first_name',
'users.last_name',
]),
["%{$term}%"]
);
}
});
}
@ -257,4 +264,37 @@ trait Searchable
return $related->getTable();
}
/**
* Builds a search string for either MySQL or sqlite by separating the provided columns with a space.
*
* @param array $columns Columns to include in search string.
* @return string
*/
private function buildMultipleColumnSearch(array $columns): string
{
$mappedColumns = collect($columns)->map(fn($column) => DB::getTablePrefix() . $column)->toArray();
$driver = config('database.connections.' . config('database.default') . '.driver');
if ($driver === 'sqlite') {
return implode("||' '||", $mappedColumns) . ' LIKE ?';
}
// Default to MySQL's concatenation method
return 'CONCAT(' . implode('," ",', $mappedColumns) . ') LIKE ?';
}
/**
* Search a string across multiple columns separated with a space.
*
* @param Builder $query
* @param array $columns - Columns to include in search string.
* @param $term
* @return Builder
*/
public function scopeOrWhereMultipleColumns($query, array $columns, $term)
{
return $query->orWhereRaw($this->buildMultipleColumnSearch($columns), ["%{$term}%"]);
}
}

View file

@ -644,14 +644,14 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
*/
public function scopeSimpleNameSearch($query, $search)
{
$query = $query->where('first_name', 'LIKE', '%'.$search.'%')
->orWhere('last_name', 'LIKE', '%'.$search.'%')
->orWhereRaw('CONCAT('.DB::getTablePrefix().'users.first_name," ",'.DB::getTablePrefix().'users.last_name) LIKE ?', ["%{$search}%"]);
return $query;
return $query->where('first_name', 'LIKE', '%' . $search . '%')
->orWhere('last_name', 'LIKE', '%' . $search . '%')
->orWhereMultipleColumns([
'users.first_name',
'users.last_name',
], $search);
}
/**
* Run additional, advanced searches.
*
@ -660,9 +660,11 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
* @return \Illuminate\Database\Eloquent\Builder
*/
public function advancedTextSearch(Builder $query, array $terms) {
foreach($terms as $term) {
$query = $query->orWhereRaw('CONCAT('.DB::getTablePrefix().'users.first_name," ",'.DB::getTablePrefix().'users.last_name) LIKE ?', ["%{$term}%"]);
$query->orWhereMultipleColumns([
'users.first_name',
'users.last_name',
], $term);
}
return $query;

View file

@ -38,24 +38,34 @@ class CustomFieldSeeder extends Seeder
[
'custom_field_id' => '1',
'custom_fieldset_id' => '1',
'order' => 0,
'required' => 0,
],
[
'custom_field_id' => '2',
'custom_fieldset_id' => '1',
'order' => 0,
'required' => 0,
],
[
'custom_field_id' => '3',
'custom_fieldset_id' => '2',
'custom_field_id' => '3',
'custom_fieldset_id' => '2',
'order' => 0,
'required' => 0,
],
[
'custom_field_id' => '4',
'custom_fieldset_id' => '2',
'custom_field_id' => '4',
'custom_fieldset_id' => '2',
'order' => 0,
'required' => 0,
],
[
'custom_field_id' => '5',
'custom_fieldset_id' => '2',
'custom_field_id' => '5',
'custom_fieldset_id' => '2',
'order' => 0,
'required' => 0,
],
]);
]);
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,8 +1,8 @@
{
"/js/build/app.js": "/js/build/app.js?id=7caeae38608edd96421f8ef59d33f5f6",
"/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=f677207c6cf9678eb539abecb408c374",
"/css/build/overrides.css": "/css/build/overrides.css?id=97a58ce3a89cd0043a1c54ecf63d4686",
"/css/build/app.css": "/css/build/app.css?id=02dbcc25fce08e9b9a9b285883821805",
"/css/build/overrides.css": "/css/build/overrides.css?id=eb44bf9eb12277e0dc680c6d498ee6af",
"/css/build/app.css": "/css/build/app.css?id=62fd4966017ec9d8740b21869eca4302",
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=dc383f8560a8d4adb51d44fb4043e03b",
"/css/dist/skins/skin-orange.css": "/css/dist/skins/skin-orange.css?id=6f0563e726c2fe4fab4026daaa5bfdf2",
"/css/dist/skins/skin-orange-dark.css": "/css/dist/skins/skin-orange-dark.css?id=e6e53eef152bba01a4c666a4d8b01117",
@ -18,7 +18,7 @@
"/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=0a82a6ae6bb4e58fe62d162c4fb50397",
"/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=da6c7997d9de2f8329142399f0ce50da",
"/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=44bf834f2110504a793dadec132a5898",
"/css/dist/all.css": "/css/dist/all.css?id=999cf8c1432f7baff31b61a04a9a8485",
"/css/dist/all.css": "/css/dist/all.css?id=654fb4ff3970fdd2f3094090ab18d098",
"/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/css/webfonts/fa-brands-400.ttf": "/css/webfonts/fa-brands-400.ttf?id=e2e2b1797606a266ed55549f5bb5a179",

View file

@ -844,8 +844,12 @@ input[type="radio"]:checked::before {
grid-template-columns: .1em auto;
gap: 1.5em;
}
.nav-tabs-custom > .nav-tabs > li {
z-index:1;
.select2-container .select2-search--inline .select2-search__field{
padding-left:15px;
}
/** --------------------------------------- **/

View file

@ -30,6 +30,19 @@ class UsersForSelectListTest extends TestCase
->assertJson(fn(AssertableJson $json) => $json->has('results', 3)->etc());
}
public function testUsersCanBeSearchedByFirstAndLastName()
{
User::factory()->create(['first_name' => 'Luke', 'last_name' => 'Skywalker']);
Passport::actingAs(User::factory()->create());
$response = $this->getJson(route('api.users.selectlist', ['search' => 'luke sky']))->assertOk();
$results = collect($response->json('results'));
$this->assertEquals(1, $results->count());
$this->assertTrue($results->pluck('text')->contains(fn($text) => str_contains($text, 'Luke')));
}
public function testUsersScopedToCompanyWhenMultipleFullCompanySupportEnabled()
{
$this->settings->enableMultipleFullCompanySupport();

View file

@ -0,0 +1,28 @@
<?php
namespace Tests\Feature\Api\Users;
use App\Models\User;
use Laravel\Passport\Passport;
use Tests\Support\InteractsWithSettings;
use Tests\TestCase;
class UsersSearchTest extends TestCase
{
use InteractsWithSettings;
public function testCanSearchByUserFirstAndLastName()
{
User::factory()->create(['first_name' => 'Luke', 'last_name' => 'Skywalker']);
User::factory()->create(['first_name' => 'Darth', 'last_name' => 'Vader']);
Passport::actingAs(User::factory()->viewUsers()->create());
$response = $this->getJson(route('api.users.index', ['search' => 'luke sky']))->assertOk();
$results = collect($response->json('rows'));
$this->assertEquals(1, $results->count());
$this->assertTrue($results->pluck('name')->contains(fn($text) => str_contains($text, 'Luke')));
$this->assertFalse($results->pluck('name')->contains(fn($text) => str_contains($text, 'Darth')));
}
}