Search improvements.

This commit is contained in:
James Cole
2019-03-02 14:12:09 +01:00
parent af07522f16
commit 87d5cabe52
16 changed files with 226 additions and 37 deletions
+4 -7
View File
@@ -67,14 +67,10 @@ class SearchController extends Controller
// parse search terms:
$searcher->parseQuery($fullQuery);
$query = $searcher->getWordsAsString();
//also get modifiers to display:
$modifiers = $searcher->getModifiers();
$subTitle = (string)trans('breadcrumbs.search_result', ['query' => $query]);
return view('search.index', compact('query', 'fullQuery', 'subTitle'));
return view('search.index', compact('query','modifiers', 'fullQuery', 'subTitle'));
}
/**
@@ -92,9 +88,10 @@ class SearchController extends Controller
$searcher->parseQuery($fullQuery);
$searcher->setLimit((int)config('firefly.search_result_limit'));
$transactions = $searcher->searchTransactions();
$searchTime = $searcher->searchTime(); // in seconds
try {
$html = view('search.search', compact('transactions'))->render();
$html = view('search.search', compact('transactions','searchTime'))->render();
// @codeCoverageIgnoreStart
} catch (Throwable $e) {
Log::error(sprintf('Cannot render search.search: %s', $e->getMessage()));
@@ -556,6 +556,25 @@ class AccountRepository implements AccountRepositoryInterface
return $result;
}
/**
* @param string $query
* @param array $types
*
* @return Collection
*/
public function searchAccount(string $query, array $types): Collection
{
$dbQuery = $this->user->accounts();
$search = sprintf('%%%s%%', $query);
if (\count($types) > 0) {
$dbQuery->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
$dbQuery->whereIn('account_types.type', $types);
}
$dbQuery->where('name', 'LIKE', $search);
return $dbQuery->get(['accounts.*']);
}
/**
* @param User $user
*/
@@ -36,6 +36,7 @@ use Illuminate\Support\Collection;
*/
interface AccountRepositoryInterface
{
/**
* Moved here from account CRUD.
*
@@ -247,6 +248,14 @@ interface AccountRepositoryInterface
*/
public function oldestJournalDate(Account $account): ?Carbon;
/**
* @param string $query
* @param array $types
*
* @return Collection
*/
public function searchAccount(string $query, array $types): Collection;
/**
* @param User $user
*/
+12
View File
@@ -623,6 +623,18 @@ class BillRepository implements BillRepositoryInterface
return $start;
}
/**
* @param string $query
*
* @return Collection
*/
public function searchBill(string $query): Collection
{
$query = sprintf('%%%s%%', $query);
return $this->user->bills()->where('name', 'LIKE', $query)->get();
}
/**
* @param User $user
*/
@@ -33,7 +33,6 @@ use Illuminate\Support\Collection;
*/
interface BillRepositoryInterface
{
/**
* @param Bill $bill
*
@@ -236,6 +235,13 @@ interface BillRepositoryInterface
*/
public function nextExpectedMatch(Bill $bill, Carbon $date): Carbon;
/**
* @param string $query
*
* @return Collection
*/
public function searchBill(string $query): Collection;
/**
* @param User $user
*/
+16 -4
View File
@@ -635,6 +635,20 @@ class BudgetRepository implements BudgetRepositoryInterface
return $result;
}
/**
* @param string $query
*
* @return Collection
*/
public function searchBudget(string $query): Collection
{
$query = sprintf('%%%s%%', $query);
return $this->user->budgets()->where('name', 'LIKE', $query)->get();
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param TransactionCurrency $currency
* @param Carbon $start
@@ -662,8 +676,6 @@ class BudgetRepository implements BudgetRepositoryInterface
return $availableBudget;
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param Budget $budget
* @param int $order
@@ -903,6 +915,8 @@ class BudgetRepository implements BudgetRepositoryInterface
return $limit;
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param Budget $budget
* @param array $data
@@ -922,8 +936,6 @@ class BudgetRepository implements BudgetRepositoryInterface
return $budget;
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param AvailableBudget $availableBudget
* @param array $data
@@ -169,8 +169,6 @@ interface BudgetRepositoryInterface
*/
public function getBudgets(): Collection;
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Get all budgets with these ID's.
*
@@ -180,6 +178,8 @@ interface BudgetRepositoryInterface
*/
public function getByIds(array $budgetIds): Collection;
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @return Collection
*/
@@ -194,6 +194,13 @@ interface BudgetRepositoryInterface
*/
public function getNoBudgetPeriodReport(Collection $accounts, Carbon $start, Carbon $end): array;
/**
* @param string $query
*
* @return Collection
*/
public function searchBudget(string $query): Collection;
/**
* @param TransactionCurrency $currency
* @param Carbon $start
@@ -531,6 +531,18 @@ class CategoryRepository implements CategoryRepositoryInterface
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param string $query
*
* @return Collection
*/
public function searchCategory(string $query): Collection
{
$query = sprintf('%%%s%%', $query);
return $this->user->categories()->where('name', 'LIKE', $query)->get();
}
/**
* @param User $user
*/
@@ -40,7 +40,6 @@ interface CategoryRepositoryInterface
*/
public function destroy(Category $category): bool;
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param Collection $categories
* @param Collection $accounts
@@ -52,6 +51,7 @@ interface CategoryRepositoryInterface
public function earnedInPeriod(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): string;
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param Collection $categories
* @param Collection $accounts
@@ -62,6 +62,8 @@ interface CategoryRepositoryInterface
*/
public function earnedInPeriodCollection(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): Collection;
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* A very cryptic method name that means:
*
@@ -119,8 +121,6 @@ interface CategoryRepositoryInterface
*/
public function getByIds(array $categoryIds): Collection;
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Returns a list of all the categories belonging to a user.
*
@@ -128,6 +128,8 @@ interface CategoryRepositoryInterface
*/
public function getCategories(): Collection;
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Return most recent transaction(journal) date or null when never used before.
*
@@ -138,8 +140,6 @@ interface CategoryRepositoryInterface
*/
public function lastUseDate(Category $category, Collection $accounts): ?Carbon;
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param Collection $categories
* @param Collection $accounts
@@ -150,6 +150,8 @@ interface CategoryRepositoryInterface
*/
public function periodExpenses(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): array;
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param Collection $accounts
* @param Carbon $start
@@ -169,8 +171,6 @@ interface CategoryRepositoryInterface
*/
public function periodIncome(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): array;
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param Collection $accounts
* @param Carbon $start
@@ -182,6 +182,15 @@ interface CategoryRepositoryInterface
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param string $query
*
* @return Collection
*/
public function searchCategory(string $query): Collection;
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param User $user
*/
+68 -6
View File
@@ -26,6 +26,11 @@ use Carbon\Carbon;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
use FireflyIII\Helpers\Filter\DoubleTransactionFilter;
use FireflyIII\Helpers\Filter\InternalTransferFilter;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\User;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
@@ -36,12 +41,22 @@ use Log;
*/
class Search implements SearchInterface
{
/** @var AccountRepositoryInterface */
private $accountRepository;
/** @var BillRepositoryInterface */
private $billRepository;
/** @var BudgetRepositoryInterface */
private $budgetRepository;
/** @var CategoryRepositoryInterface */
private $categoryRepository;
/** @var int */
private $limit = 100;
/** @var Collection */
private $modifiers;
/** @var string */
private $originalQuery = '';
/** @var float */
private $startTime;
/** @var User */
private $user;
/** @var array */
@@ -54,8 +69,13 @@ class Search implements SearchInterface
*/
public function __construct()
{
$this->modifiers = new Collection;
$this->validModifiers = (array)config('firefly.search_modifiers');
$this->modifiers = new Collection;
$this->validModifiers = (array)config('firefly.search_modifiers');
$this->startTime = microtime(true);
$this->accountRepository = app(AccountRepositoryInterface::class);
$this->categoryRepository = app(CategoryRepositoryInterface::class);
$this->budgetRepository = app(BudgetRepositoryInterface::class);
$this->billRepository = app(BillRepositoryInterface::class);
if ('testing' === config('app.env')) {
Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this)));
@@ -112,6 +132,14 @@ class Search implements SearchInterface
}
}
/**
* @return float
*/
public function searchTime(): float
{
return microtime(true) - $this->startTime;
}
/**
* @return LengthAwarePaginator
*/
@@ -128,8 +156,6 @@ class Search implements SearchInterface
$collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation();
}
$collector->setSearchWords($this->words);
$collector->removeFilter(InternalTransferFilter::class);
$collector->addFilter(DoubleTransactionFilter::class);
@@ -155,6 +181,10 @@ class Search implements SearchInterface
public function setUser(User $user): void
{
$this->user = $user;
$this->accountRepository->setUser($user);
$this->billRepository->setUser($user);
$this->categoryRepository->setUser($user);
$this->budgetRepository->setUser($user);
}
/**
@@ -167,8 +197,6 @@ class Search implements SearchInterface
{
/*
* TODO:
* 'source', 'destination',
* 'category','budget',
* 'bill',
*/
@@ -176,6 +204,40 @@ class Search implements SearchInterface
switch ($modifier['type']) {
default:
die(sprintf('unsupported modifier: "%s"', $modifier['type']));
case 'source':
// source can only be asset, liability or revenue account:
$searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::REVENUE];
$accounts = $this->accountRepository->searchAccount($modifier['value'], $searchTypes);
if ($accounts->count() > 0) {
$collector->setAccounts($accounts);
}
break;
case 'destination':
// source can only be asset, liability or expense account:
$searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE];
$accounts = $this->accountRepository->searchAccount($modifier['value'], $searchTypes);
if ($accounts->count() > 0) {
$collector->setOpposingAccounts($accounts);
}
break;
case 'category':
$result = $this->categoryRepository->searchCategory($modifier['value']);
if ($result->count() > 0) {
$collector->setCategories($result);
}
break;
case 'bill':
$result = $this->billRepository->searchBill($modifier['value']);
if ($result->count() > 0) {
$collector->setBills($result);
}
break;
case 'budget':
$result = $this->budgetRepository->searchBudget($modifier['value']);
if ($result->count() > 0) {
$collector->setBudgets($result);
}
break;
case 'amount_is':
case 'amount':
$amount = app('steam')->positive((string)$modifier['value']);
+10
View File
@@ -31,6 +31,11 @@ use Illuminate\Support\Collection;
*/
interface SearchInterface
{
/**
* @return Collection
*/
public function getModifiers(): Collection;
/**
* @return string
*/
@@ -46,6 +51,11 @@ interface SearchInterface
*/
public function parseQuery(string $query);
/**
* @return float
*/
public function searchTime(): float;
/**
* @return LengthAwarePaginator
*/
-2
View File
@@ -39,8 +39,6 @@ function searchFailure() {
function presentSearchResults(data) {
$('.search_ongoing').hide();
$('p.search_count').show();
$('span.search_count').text(data.count);
$('.search_box').find('.overlay').remove();
$('.search_results').html(data.html).show();
+21 -1
View File
@@ -217,7 +217,27 @@ return [
// search
'search' => 'Search',
'search_query' => 'Query',
'search_found_transactions' => 'Number of transactions found:',
'search_found_transactions' => 'Firefly III found :count transaction(s) in :time seconds.',
'search_for_query' => 'Firefly III is searching for transactions with all of these words in them: <span class="text-info">:query</span>',
'search_modifier_amount_is' => 'Amount is exactly :value',
'search_modifier_amount' => 'Amount is exactly :value',
'search_modifier_amount_max' => 'Amount is at most :value',
'search_modifier_amount_min' => 'Amount is at least :value',
'search_modifier_amount_less' => 'Amount is less than :value',
'search_modifier_amount_more' => 'Amount is more than :value',
'search_modifier_source' => 'Source account is :value',
'search_modifier_destination' => 'Destination account is :value',
'search_modifier_category' => 'Category is :value',
'search_modifier_budget' => 'Budget is :value',
'search_modifier_bill' => 'Bill is :value',
'search_modifier_type' => 'Transaction type is :value',
'search_modifier_date' => 'Transaction date is :value',
'search_modifier_date_before' => 'Transaction date is before :value',
'search_modifier_date_after' => 'Transaction date is after :value',
'search_modifier_on' => 'Transaction date is :value',
'search_modifier_before' => 'Transaction date is before :value',
'search_modifier_after' => 'Transaction date is after :value',
'modifiers_applies_are' => 'The following modifiers are applied to the search as well:',
'general_search_error' => 'An error occured while searching. Please check the log files for more information.',
'search_box' => 'Search',
'search_box_intro' => 'Welcome to the search function of Firefly III. Enter your search query in the box. Make sure you check out the help file because the search is pretty advanced.',
+14 -6
View File
@@ -20,7 +20,8 @@
<div class="form-group">
<label for="query" class="col-sm-1 control-label">{{ 'search_query'|_ }}</label>
<div class="col-sm-10">
<input autocomplete="off" type="text" name="q" id="query" value="{{ fullQuery }}" class="form-control" placeholder="{{ fullQuery }}">
<input autocomplete="off" type="text" name="q" id="query" value="{{ fullQuery }}" class="form-control"
placeholder="{{ fullQuery }}">
</div>
</div>
<div class="form-group">
@@ -29,6 +30,17 @@
</div>
</div>
</form>
<p>
{{ trans('firefly.search_for_query', {query: query})|raw}}
</p>
{% if modifiers|length > 0 %}
<p>{{ trans('firefly.modifiers_applies_are') }}</p>
<ul>
{% for modifier in modifiers %}
<li>{{ trans('firefly.search_modifier_'~modifier.type, {value: modifier.value}) }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
</div>
@@ -44,11 +56,7 @@
<p class="search_ongoing text-center" style="margin-top:70px;">
{{ 'search_searching'|_ }}
</p>
<p class="search_count" style="display: none;">
{{ 'search_found_transactions'|_ }} <span class="search_count"></span>
</p>
<div class="search_results" style="display: none;">
</div>
<div class="search_results" style="display: none;"></div>
{# loading indicator #}
<div class="overlay">
<i class="fa fa-refresh fa-spin"></i>
+5
View File
@@ -1,3 +1,8 @@
<p>
<p class="search_count">
{{ trans('firefly.search_found_transactions', {count: transactions.count, time: searchTime}) }}
</p>
<table class="table table-hover table-condensed">
<thead>
<tr class="ignore">
@@ -24,6 +24,7 @@ namespace Tests\Feature\Controllers;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Search\SearchInterface;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Log;
use Mockery;
@@ -61,6 +62,7 @@ class SearchControllerTest extends TestCase
$search->shouldReceive('parseQuery')->once();
$search->shouldReceive('getWordsAsString')->once()->andReturn('test');
$search->shouldReceive('getModifiers')->once()->andReturn(new Collection);
$this->be($this->user());
$response = $this->get(route('search.index') . '?q=test');
$response->assertStatus(200);
@@ -78,7 +80,8 @@ class SearchControllerTest extends TestCase
$search->shouldReceive('parseQuery')->once();
$search->shouldReceive('setLimit')->withArgs([50])->once();
$search->shouldReceive('searchTransactions')->once()->andReturn(new Collection);
$search->shouldReceive('searchTransactions')->once()->andReturn(new LengthAwarePaginator([],0,10));
$search->shouldReceive('searchTime')->once()->andReturn(0.2);
$this->be($this->user());