Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions app/Http/Requests/UserListRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ public function rules()
{
return [
'orderBy' => 'in:first_name,last_name,organisation,industry_type,created_at,last_logged_in_at',
'sort' => 'in:asc,desc'
'sort' => 'in:asc,desc',
'filters.search' => 'nullable|string|min:3|max:191'
];
}

Expand All @@ -36,8 +37,10 @@ public function getUserQuery(): UserQuery
$userQuery->setOrderBy($orderBy);
$userQuery->setSort($sort);

foreach ($this->get('filters', []) as $column => $value) {
$userQuery->addFilter($column, $value);
$filters = $this->get('filters', $this->get('filters\\', []));

foreach ($filters as $column => $value) {
$userQuery->addFilter(rtrim($column, '\\'), $value);
}

return $userQuery;
Expand Down
20 changes: 19 additions & 1 deletion app/Repositories/Access/UserRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,11 @@ public function queryUsers(UserQuery $userQuery): Builder
});
});

$search = trim($userQuery->getFilters()->get('search', ''));
if (mb_strlen($search) >= 3) {
$this->applySearchFilter($builder, $search);
}

$society = $userQuery->getFilters()->only(['society'])->first();
if ($society) {
$builder->whereHas('organisations', function ($query) use ($society) {
Expand Down Expand Up @@ -214,12 +219,25 @@ public function queryUsers(UserQuery $userQuery): Builder
}

if (in_array($userQuery->getOrderBy(), ['first_name', 'last_name', 'organisation', 'industry_type'])) {
$builder->orderByRaw('(SELECT ' . $userQuery->getOrderBy() . ' FROM user_profiles WHERE user_profiles.user_id = users.id) ' . $userQuery->getSort());
$builder->orderByRaw('(SELECT ' . $userQuery->getOrderBy() . ' FROM user_profiles WHERE user_profiles.user_id = users.id AND user_profiles.deleted_at IS NULL ORDER BY user_profiles.id DESC LIMIT 1) ' . $userQuery->getSort());
}

return $builder;
}

private function applySearchFilter(Builder $builder, string $search)
{
$profileMatch = 'MATCH(user_profiles.first_name, user_profiles.last_name, user_profiles.organisation) AGAINST (? IN NATURAL LANGUAGE MODE)';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove filter by organization

$emailMatch = 'MATCH(users.email) AGAINST (? IN NATURAL LANGUAGE MODE)';
$profileScore = "(SELECT {$profileMatch} FROM user_profiles WHERE user_profiles.user_id = users.id AND user_profiles.deleted_at IS NULL ORDER BY user_profiles.id DESC LIMIT 1)";
$score = "({$emailMatch} + COALESCE({$profileScore}, 0))";

$builder->select('users.*')
->selectRaw("{$score} as search_score", [$search, $search])
->whereRaw("({$emailMatch} > 0 OR EXISTS (SELECT 1 FROM user_profiles WHERE user_profiles.user_id = users.id AND user_profiles.deleted_at IS NULL AND {$profileMatch} > 0))", [$search, $search])
->orderBy('search_score', 'desc');
}

public function deactivate(User $user)
{
$user->activated = false;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;

class AddFulltextIndexesForUserSearch extends Migration
{

public function up()
{
if (env('DB_CONNECTION') !== 'mysql') {
return;
}

if (!$this->indexExists('users', 'users_email_fulltext_search')) {
DB::statement('ALTER TABLE users ADD FULLTEXT users_email_fulltext_search(email)');
}

$this->dropIndexIfExists('user_profiles', 'user_profiles_search_fulltext');
DB::statement('ALTER TABLE user_profiles ADD FULLTEXT user_profiles_search_fulltext(first_name, last_name, organisation)');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove organisation

}


public function down()
{
if (env('DB_CONNECTION') !== 'mysql') {
return;
}

$this->dropIndexIfExists('users', 'users_email_fulltext_search');
$this->dropIndexIfExists('user_profiles', 'user_profiles_search_fulltext');
}

private function dropIndexIfExists(string $table, string $index)
{
if ($this->indexExists($table, $index)) {
DB::statement("ALTER TABLE {$table} DROP INDEX {$index}");
}
}

private function indexExists(string $table, string $index): bool
{
return (bool) DB::select(
DB::raw(
"SHOW KEYS
FROM {$table}
WHERE Key_name='{$index}'"
)
);
}
}
54 changes: 51 additions & 3 deletions resources/assets/js/pages/users/list.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@
:staynull="true"
v-if="!apiUsers"/>
</b-col>
<b-col cols="12" md="6" lg="4" xl="3" class="ml-lg-auto">
<p class="select-header"> {{ $t('users.list.search') }}</p>
<b-form-input
v-model.trim="searchFilter"
class="search-filter-input"
type="text"
:disabled="fetchingUsers"
:placeholder="$t('users.list.search_placeholder')">
</b-form-input>
</b-col>
<b-col class="text-right">
<b-button @click="clearFilters" :disabled="noFilters" class="btn-outline-primary clear-filter-btn">
{{ $t('users.list.clear_filters') }}
Expand Down Expand Up @@ -226,6 +236,7 @@ export default {
fetchingUsers: false,
roles: null,
locationOptions: null,
searchDebounce: null,
countries: require('country-list')()
}
},
Expand All @@ -239,6 +250,21 @@ export default {
deep: true
},
activatedFilter: fetchHandler,
searchFilter: {
handler (val, oldVal) {
if (val !== oldVal) {
clearTimeout(this.searchDebounce)
const search = val ? val.trim() : ''
if (search.length > 0 && search.length < 3) {
return
}
this.searchDebounce = setTimeout(() => {
this.currentPage = 1
this.fetchUsers()
}, 350)
}
}
},
roleFilter: fetchHandler,
countryFilter: fetchHandler,
termsFilter: fetchHandler,
Expand All @@ -259,6 +285,9 @@ export default {
this.fetchUsers()
this.fetchTerms()
},
beforeDestroy () {
clearTimeout(this.searchDebounce)
},
metaInfo () {
return { title: this.$t('users.list.manage') }
},
Expand All @@ -268,6 +297,7 @@ export default {
this.roleFilter = null
this.countryFilter = null
this.selectedSoc = null
this.searchFilter = ''
this.termsFilter = termsDefault
},
getSocietyByCode (code) {
Expand Down Expand Up @@ -310,12 +340,14 @@ export default {
},
async fetchUsers () {
this.fetchingUsers = true
await this.fetchRoles()
if (this.rolesEmpty) {
await this.fetchRoles()
}

let apiUserRole = this.roleOptions.find(role => role.name === 'API User')
const apiUserRole = this.roleOptions.find(role => role.name === 'API User')
let filterRoleId = this.roleFilter
if (filterRoleId === null) {
filterRoleId = this.apiUsers ? apiUserRole.id : null
filterRoleId = this.apiUsers && apiUserRole ? apiUserRole.id : null
}

if (!apiUserRole && this.apiUsers) {
Expand All @@ -330,6 +362,7 @@ export default {
role: filterRoleId,
society: this.selectedSoc ? this.selectedSoc.countryCode : null,
country_code: this.countryFilter,
search: this.searchFilter,
terms_version: this.termsFilter === termsDefault ? null : this.termsFilter
},
admin: !this.apiUsers && filterRoleId === null,
Expand Down Expand Up @@ -408,6 +441,14 @@ export default {
return this.$store.state.users.filters.roleFilter
}
},
searchFilter: {
set: function (newVal) {
this.$store.dispatch('users/setFilter', { searchFilter: newVal })
},
get: function () {
return this.$store.state.users.filters.searchFilter
}
},
countryFilter: {
set: function(newVal) {
this.$store.dispatch('users/setFilter', { countryFilter: newVal })
Expand Down Expand Up @@ -451,6 +492,7 @@ export default {
this.roleFilter === null &&
this.countryFilter === null &&
this.selectedSoc === null &&
!this.searchFilter &&
this.termsFilter === termsDefault
},
...mapGetters({
Expand Down Expand Up @@ -487,5 +529,11 @@ export default {
.text-nowrap {
white-space: nowrap;
}
.search-filter-input {
background: #E9E9E9;
border: none;
border-radius: 10px;
height: 2.45rem;
}

</style>
4 changes: 4 additions & 0 deletions resources/assets/js/plugins/axios.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ axios.interceptors.request.use(request => {

// Response interceptor
axios.interceptors.response.use(response => response, error => {
if (axios.isCancel(error)) {
return Promise.reject(error)
}

if (error.response && error.response.status) {
const { status } = error.response

Expand Down
13 changes: 13 additions & 0 deletions resources/assets/js/store/modules/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import axios from 'axios'
import * as types from '../mutation-types'
import querystring from 'querystring'

let latestFetchUsersRequest = 0

// state
export const state = {
users: {
Expand All @@ -24,6 +26,7 @@ export const state = {
roleFilter: null,
countryFilter: null,
selectedSoc: null,
searchFilter: '',
termsFilter: 'Terms and Conditions'
}
}
Expand Down Expand Up @@ -104,6 +107,7 @@ export const actions = {
roleFilter: null,
countryFilter: null,
selectedSoc: null,
searchFilter: '',
termsFilter: 'Terms and Conditions'
})
commit(types.SET_ORDER_BY, null)
Expand All @@ -120,6 +124,7 @@ export const actions = {
commit(types.SET_SORT_DESC, sortDesc)
},
async fetchUsers ({ commit }, { page, filters, excludes, admin, orderBy, sort }) {
const requestId = ++latestFetchUsersRequest
const queryOptions = { page }
if (orderBy !== null) {
queryOptions.orderBy = orderBy
Expand All @@ -131,11 +136,19 @@ export const actions = {
filterString += filters.society !== null ? `&filters[society]=${filters.society}` : ''
filterString += filters.country_code !== null ? `&filters[country_code]=${filters.country_code}` : ''
filterString += filters.terms_version !== null ? `&filters[terms_version]=${filters.terms_version}` : ''
const search = filters.search ? filters.search.trim() : ''
filterString += search.length >= 3 ? `&filters[search]=${encodeURIComponent(search)}` : ''

const url = admin ? '/api/users/admins' : '/api/users'
const { data } = await axios.get(`${url}?${querystring.stringify(queryOptions)}${filterString}`)
if (requestId !== latestFetchUsersRequest) {
return
}
commit(types.FETCH_USERS_SUCCESS, { users: data })
} catch (e) {
if (requestId !== latestFetchUsersRequest) {
return
}
commit(types.FETCH_USERS_FAILURE, { error: e })
}
},
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/am.json
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@
"select_country": "ሀገር ምረጥ",
"select_terms": "የውሎች ስሪት ምረጥ",
"select_society": "የብሔራዊ ማህበር ምረጥ",
"search": "ፈልግ",
"search_placeholder": "ስም፣ ኢሜይል ወይም ድርጅት (ቢያንስ 3 ቁምፊዎች)",
"create": "አዲስ መጠቀሚያ ይፍጠሩ፤",
"download_report": "ሪፖርት ያውርዱ፤",
"empty": "ምንም መጠቀሚያ ማግኘት አልተቻለም፤",
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@
"select_country": "اختر بلدًا",
"select_terms": "اختر إصدار الشروط",
"select_society": "اختر جمعية وطنية",
"search": "بحث",
"search_placeholder": "الاسم أو البريد الإلكتروني أو المؤسسة (3 أحرف على الأقل)",
"create": "إنشاء مستخدم جديد",
"download_report": "تنزيل التقرير",
"empty": "لم يتم العثور على أي مستخدمين",
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/bn.json
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@
"select_country": "একটি দেশ নির্বাচন করুন",
"select_terms": "একটি শর্তাবলী সংস্করণ নির্বাচন করুন",
"select_society": "জাতীয় সমিতি নির্বাচন করুন",
"search": "অনুসন্ধান",
"search_placeholder": "নাম, ইমেল বা সংস্থা (কমপক্ষে ৩ অক্ষর)",
"create": "নতুন ব্যবহারকারী তৈরি করুন",
"download_report": "রিপোর্ট ডাউনলোড করুন",
"empty": "কোন ব্যবহারকারী খুঁজে পাইনি",
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@
"select_country": "Land auswählen",
"select_terms": "Version der Bedingungen auswählen",
"select_society": "Nationale Gesellschaft auswählen",
"search": "Suchen",
"search_placeholder": "Name, E-Mail oder Organisation (mind. 3 Zeichen)",
"create": "Neuen Benutzer anlegen",
"download_report": "Bericht herunterladen",
"empty": "Es konnten keine Benutzer gefunden werden",
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@
"select_country": "Select a country",
"select_terms": "Select a terms version",
"select_society": "Select a nacional society",
"search": "Search",
"search_placeholder": "Name, email or organization (min. 3 characters)",
"create": "Create new user",
"download_report": "Download report",
"empty": "Couldn't find any users",
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@
"select_country": "Seleccionar un país",
"select_terms": "Seleccionar una versión de términos",
"select_society": "Seleccionar una sociedad nacional",
"search": "Buscar",
"search_placeholder": "Nombre, correo u organización (mín. 3 caracteres)",
"create": "Crear nuevo usuario",
"download_report": "Descargar informe",
"empty": "No se encontraron usuarios",
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@
"select_country": "Sélectionner un pays",
"select_terms": "Sélectionner une version des conditions",
"select_society": "Sélectionner une société nationale",
"search": "Rechercher",
"search_placeholder": "Nom, e-mail ou organisation (3 caractères min.)",
"create": "Créer un nouvel utilisateur",
"download_report": "Télécharger le rapport",
"empty": "Aucun utilisateur trouvé",
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/ht.json
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@
"select_country": "Chwazi yon peyi",
"select_terms": "Chwazi yon vèsyon tèm yo",
"select_society": "Chwazi yon sosyete nasyonal",
"search": "Chèche",
"search_placeholder": "Non, imel oswa òganizasyon (omwen 3 karaktè)",
"create": "Kreye nouvo itilizatè",
"download_report": "Telechaje rapò",
"empty": "Yo pa jwenn itilizatè yo",
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/id.json
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@
"select_country": "Select a country",
"select_terms": "Select a terms version",
"select_society": "Select a nacional society",
"search": "Cari",
"search_placeholder": "Nama, email, atau organisasi (min. 3 karakter)",
"create": "Membuat pengguna baru",
"download_report": "Unduh laporan",
"empty": "Tidak dapat menemukan pengguna",
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@
"select_country": "Seleziona un paese",
"select_terms": "Seleziona una versione dei termini",
"select_society": "Seleziona una società nazionale",
"search": "Cerca",
"search_placeholder": "Nome, email o organizzazione (min. 3 caratteri)",
"create": "Crea nuovo utente",
"download_report": "Scarica il report",
"empty": "Non siamo riusciti a trovare alcun utente",
Expand Down
Loading