This commit is contained in:
James Cole 2023-12-03 07:02:23 +01:00
parent a5c370c70e
commit 3136bccb24
No known key found for this signature in database
GPG Key ID: B49A324B7EAD6D80
7 changed files with 170 additions and 6 deletions

View File

@ -917,6 +917,7 @@ trait MetaCollection
$list = $tags->pluck('tag')->toArray(); $list = $tags->pluck('tag')->toArray();
$filter = static function (array $object) use ($list): bool { $filter = static function (array $object) use ($list): bool {
foreach ($object['transactions'] as $transaction) { foreach ($object['transactions'] as $transaction) {
app('log')->debug(sprintf('Transaction has %d tag(s)', count($transaction['tags'])));
foreach ($transaction['tags'] as $tag) { foreach ($transaction['tags'] as $tag) {
if (in_array($tag['name'], $list, true)) { if (in_array($tag['name'], $list, true)) {
return false; return false;

View File

@ -515,7 +515,6 @@ class GroupCollector implements GroupCollectorInterface
// add to query: // add to query:
$this->query->orWhereIn('transaction_journals.transaction_group_id', $groupIds); $this->query->orWhereIn('transaction_journals.transaction_group_id', $groupIds);
} }
$result = $this->query->get($this->fields); $result = $this->query->get($this->fields);
// now to parse this into an array. // now to parse this into an array.
@ -823,10 +822,15 @@ class GroupCollector implements GroupCollectorInterface
private function postFilterCollection(Collection $collection): Collection private function postFilterCollection(Collection $collection): Collection
{ {
$currentCollection = $collection; $currentCollection = $collection;
app('log')->debug(sprintf('GroupCollector: postFilterCollection has %d filter(s) and %d transaction(s).', count($this->postFilters), count($currentCollection)));
/** /**
* @var Closure $function * @var Closure $function
*/ */
foreach ($this->postFilters as $function) { foreach ($this->postFilters as $function) {
app('log')->debug('Applying filter...');
$nextCollection = new Collection(); $nextCollection = new Collection();
// loop everything in the current collection // loop everything in the current collection
// and save it (or not) in the new collection. // and save it (or not) in the new collection.
@ -843,6 +847,7 @@ class GroupCollector implements GroupCollectorInterface
$nextCollection->push($item); $nextCollection->push($item);
} }
$currentCollection = $nextCollection; $currentCollection = $nextCollection;
app('log')->debug(sprintf('GroupCollector: postFilterCollection has %d transaction(s) left.', count($currentCollection)));
} }
return $currentCollection; return $currentCollection;
} }

View File

@ -87,7 +87,7 @@ class TagRepository implements TagRepositoryInterface
*/ */
public function get(): Collection public function get(): Collection
{ {
return $this->user->tags()->orderBy('tag', 'ASC')->get(); return $this->user->tags()->orderBy('tag', 'ASC')->get(['tags.*']);
} }
/** /**
@ -454,4 +454,24 @@ class TagRepository implements TagRepositoryInterface
/** @var Location|null */ /** @var Location|null */
return $tag->locations()->first(); return $tag->locations()->first();
} }
/**
* @inheritDoc
*/
public function tagStartsWith(string $query): Collection
{
$search = sprintf('%s%%', $query);
return $this->user->tags()->where('tag', 'LIKE', $search)->get(['tags.*']);
}
/**
* @inheritDoc
*/
public function tagEndsWith(string $query): Collection
{
$search = sprintf('%%%s', $query);
return $this->user->tags()->where('tag', 'LIKE', $search)->get(['tags.*']);
}
} }

View File

@ -153,6 +153,24 @@ interface TagRepositoryInterface
*/ */
public function searchTag(string $query): Collection; public function searchTag(string $query): Collection;
/**
* Find one or more tags that start with the string in the query
*
* @param string $query
*
* @return Collection
*/
public function tagStartsWith(string $query): Collection;
/**
* Find one or more tags that start with the string in the query
*
* @param string $query
*
* @return Collection
*/
public function tagEndsWith(string $query): Collection;
/** /**
* Search the users tags. * Search the users tags.
* *

View File

@ -57,7 +57,6 @@ use Gdbots\QueryParser\QueryParser;
use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use LogicException; use LogicException;
use PragmaRX\Random\Generators\StringGenerator;
use TypeError; use TypeError;
/** /**
@ -82,6 +81,9 @@ class OperatorQuerySearch implements SearchInterface
private array $validOperators; private array $validOperators;
private array $words; private array $words;
private array $excludeTags;
private array $includeTags;
/** /**
* OperatorQuerySearch constructor. * OperatorQuerySearch constructor.
* *
@ -93,6 +95,8 @@ class OperatorQuerySearch implements SearchInterface
$this->operators = new Collection(); $this->operators = new Collection();
$this->page = 1; $this->page = 1;
$this->words = []; $this->words = [];
$this->excludeTags = [];
$this->includeTags = [];
$this->prohibitedWords = []; $this->prohibitedWords = [];
$this->invalidOperators = []; $this->invalidOperators = [];
$this->limit = 25; $this->limit = 25;
@ -167,6 +171,8 @@ class OperatorQuerySearch implements SearchInterface
foreach ($query1->getNodes() as $searchNode) { foreach ($query1->getNodes() as $searchNode) {
$this->handleSearchNode($searchNode); $this->handleSearchNode($searchNode);
} }
$this->parseTagInstructions();
$this->collector->setSearchWords($this->words); $this->collector->setSearchWords($this->words);
$this->collector->excludeSearchWords($this->prohibitedWords); $this->collector->excludeSearchWords($this->prohibitedWords);
@ -868,7 +874,8 @@ class OperatorQuerySearch implements SearchInterface
case 'tag_is': case 'tag_is':
$result = $this->tagRepository->findByTag($value); $result = $this->tagRepository->findByTag($value);
if (null !== $result) { if (null !== $result) {
$this->collector->setTags(new Collection([$result])); $this->includeTags[] = $result->id;
$this->includeTags = array_unique($this->includeTags);
} }
// no tags found means search must result in nothing. // no tags found means search must result in nothing.
if (null === $result) { if (null === $result) {
@ -876,11 +883,79 @@ class OperatorQuerySearch implements SearchInterface
$this->collector->findNothing(); $this->collector->findNothing();
} }
break; break;
case 'tag_contains':
$tags = $this->tagRepository->searchTag($value);
if (0 === $tags->count()) {
app('log')->info(sprintf('No valid tags in "%s"-operator, so search will not return ANY results.', $operator));
$this->collector->findNothing();
}
if ($tags->count() > 0) {
$ids = array_values($tags->pluck('id')->toArray());
$this->includeTags = array_unique(array_merge($this->includeTags, $ids));
}
break;
case 'tag_starts':
$tags = $this->tagRepository->tagStartsWith($value);
if (0 === $tags->count()) {
app('log')->info(sprintf('No valid tags in "%s"-operator, so search will not return ANY results.', $operator));
$this->collector->findNothing();
}
if ($tags->count() > 0) {
$ids = array_values($tags->pluck('id')->toArray());
$this->includeTags = array_unique(array_merge($this->includeTags, $ids));
}
break;
case '-tag_starts':
$tags = $this->tagRepository->tagStartsWith($value);
if (0 === $tags->count()) {
app('log')->info(sprintf('No valid tags in "%s"-operator, so search will not return ANY results.', $operator));
$this->collector->findNothing();
}
if ($tags->count() > 0) {
$ids = array_values($tags->pluck('id')->toArray());
$this->excludeTags = array_unique(array_merge($this->includeTags, $ids));
}
break;
case 'tag_ends':
$tags = $this->tagRepository->tagEndsWith($value);
if (0 === $tags->count()) {
app('log')->info(sprintf('No valid tags in "%s"-operator, so search will not return ANY results.', $operator));
$this->collector->findNothing();
}
if ($tags->count() > 0) {
$ids = array_values($tags->pluck('id')->toArray());
$this->includeTags = array_unique(array_merge($this->includeTags, $ids));
}
break;
case '-tag_ends':
$tags = $this->tagRepository->tagEndsWith($value);
if (0 === $tags->count()) {
app('log')->info(sprintf('No valid tags in "%s"-operator, so search will not return ANY results.', $operator));
$this->collector->findNothing();
}
if ($tags->count() > 0) {
$ids = array_values($tags->pluck('id')->toArray());
$this->excludeTags = array_unique(array_merge($this->includeTags, $ids));
}
break;
case '-tag_contains':
$tags = $this->tagRepository->searchTag($value)->keyBy('id');
if (0 === $tags->count()) {
app('log')->info(sprintf('No valid tags in "%s"-operator, so search will not return ANY results.', $operator));
$this->collector->findNothing();
}
if ($tags->count() > 0) {
$ids = array_values($tags->pluck('id')->toArray());
$this->excludeTags = array_unique(array_merge($this->excludeTags, $ids));
}
break;
case '-tag_is': case '-tag_is':
case 'tag_is_not': case 'tag_is_not':
$result = $this->tagRepository->searchTag($value); $result = $this->tagRepository->searchTag($value);
if ($result->count() > 0) { if ($result->count() > 0) {
$this->collector->setWithoutSpecificTags($result); $this->excludeTags[] = $result->id;
$this->excludeTags = array_unique($this->excludeTags);
} }
break; break;
// //
@ -1443,7 +1518,7 @@ class OperatorQuerySearch implements SearchInterface
* *
* @param string $value * @param string $value
* @param SearchDirection $searchDirection * @param SearchDirection $searchDirection
* @param StringPosition $stringPosition * @param StringPosition $stringPosition
* @param bool $prohibited * @param bool $prohibited
* @SuppressWarnings(PHPMD.BooleanArgumentFlag) * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
*/ */
@ -2160,4 +2235,42 @@ class OperatorQuerySearch implements SearchInterface
$this->limit = $limit; $this->limit = $limit;
$this->collector->setLimit($this->limit); $this->collector->setLimit($this->limit);
} }
/**
* @return void
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function parseTagInstructions(): void
{
app('log')->debug('Now in parseTagInstructions()');
// if exclude tags, remove excluded tags.
if (count($this->excludeTags) > 0) {
app('log')->debug(sprintf('%d exclude tag(s)', count($this->excludeTags)));
$collection = new Collection;
foreach ($this->excludeTags as $tagId) {
$tag = $this->tagRepository->find($tagId);
if (null !== $tag) {
app('log')->debug(sprintf('Exclude tag "%s"', $tag->tag));
$collection->push($tag);
}
}
app('log')->debug(sprintf('Selecting all tags except %d excluded tag(s).', $collection->count()));
$this->collector->setWithoutSpecificTags($collection);
}
// if include tags, include them:
if (count($this->includeTags) > 0) {
app('log')->debug(sprintf('%d include tag(s)', count($this->includeTags)));
$collection = new Collection;
foreach ($this->includeTags as $tagId) {
$tag = $this->tagRepository->find($tagId);
if (null !== $tag) {
app('log')->debug(sprintf('Include tag "%s"', $tag->tag));
$collection->push($tag);
}
}
$this->collector->setTags($collection);
}
}
} }

View File

@ -34,6 +34,9 @@ return [
'tag_is' => ['alias' => false, 'needs_context' => true,], 'tag_is' => ['alias' => false, 'needs_context' => true,],
'tag_is_not' => ['alias' => false, 'needs_context' => true,], 'tag_is_not' => ['alias' => false, 'needs_context' => true,],
'tag' => ['alias' => true, 'alias_for' => 'tag_is', 'needs_context' => true,], 'tag' => ['alias' => true, 'alias_for' => 'tag_is', 'needs_context' => true,],
'tag_contains' => ['alias' => false, 'needs_context' => true,],
'tag_ends' => ['alias' => false, 'needs_context' => true,],
'tag_starts' => ['alias' => false, 'needs_context' => true,],
'description_is' => ['alias' => false, 'needs_context' => true,], 'description_is' => ['alias' => false, 'needs_context' => true,],
'description' => ['alias' => true, 'alias_for' => 'description_is', 'needs_context' => true,], 'description' => ['alias' => true, 'alias_for' => 'description_is', 'needs_context' => true,],
'description_contains' => ['alias' => false, 'needs_context' => true,], 'description_contains' => ['alias' => false, 'needs_context' => true,],

View File

@ -449,6 +449,10 @@ return [
'search_modifier_transaction_type' => 'Transaction type is ":value"', 'search_modifier_transaction_type' => 'Transaction type is ":value"',
'search_modifier_not_transaction_type' => 'Transaction type is not ":value"', 'search_modifier_not_transaction_type' => 'Transaction type is not ":value"',
'search_modifier_tag_is' => 'Tag is ":value"', 'search_modifier_tag_is' => 'Tag is ":value"',
'search_modifier_tag_contains' => 'Tag contains ":value"',
'search_modifier_not_tag_contains' => 'Tag does not contain ":value"',
'search_modifier_tag_ends' => 'Tag ends with ":value"',
'search_modifier_tag_starts' => 'Tag starts with ":value"',
'search_modifier_not_tag_is' => 'No tag is ":value"', 'search_modifier_not_tag_is' => 'No tag is ":value"',
'search_modifier_date_on_year' => 'Transaction is in year ":value"', 'search_modifier_date_on_year' => 'Transaction is in year ":value"',
'search_modifier_not_date_on_year' => 'Transaction is not in year ":value"', 'search_modifier_not_date_on_year' => 'Transaction is not in year ":value"',