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

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()));

View File

@@ -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
*/

View File

@@ -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
*/

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
*/

View File

@@ -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
*/

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

View File

@@ -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

View File

@@ -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
*/

View File

@@ -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
*/

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 */
@@ -56,6 +71,11 @@ class Search implements SearchInterface
{
$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']);

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
*/

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();

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.',

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>

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">

View File

@@ -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());