Added autocomplete for part parameters

This commit is contained in:
Jan Böhmer 2022-09-05 17:02:57 +02:00
parent 44b288b807
commit 9a7e47863b
18 changed files with 209 additions and 134 deletions

View file

@ -42,9 +42,22 @@ declare(strict_types=1);
namespace App\Controller;
use App\Entity\Parameters\AttachmentTypeParameter;
use App\Entity\Parameters\CategoryParameter;
use App\Entity\Parameters\DeviceParameter;
use App\Entity\Parameters\FootprintParameter;
use App\Entity\Parameters\GroupParameter;
use App\Entity\Parameters\ManufacturerParameter;
use App\Entity\Parameters\MeasurementUnitParameter;
use App\Entity\Parameters\PartParameter;
use App\Entity\Parameters\StorelocationParameter;
use App\Entity\Parameters\SupplierParameter;
use App\Entity\PriceInformations\Currency;
use App\Repository\ParameterRepository;
use App\Services\Attachments\AttachmentURLGenerator;
use App\Services\Attachments\BuiltinAttachmentsFinder;
use App\Services\TagFinder;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Asset\Packages;
use Symfony\Component\HttpFoundation\JsonResponse;
@ -99,6 +112,58 @@ class TypeaheadController extends AbstractController
return new JsonResponse($data, 200, [], true);
}
/**
* This functions map the parameter type to the class, so we can access its repository
* @param string $type
* @return class-string
*/
private function typeToParameterClass(string $type): string
{
switch ($type) {
case 'category':
return CategoryParameter::class;
case 'part':
return PartParameter::class;
case 'device':
return DeviceParameter::class;
case 'footprint':
return FootprintParameter::class;
case 'manufacturer':
return ManufacturerParameter::class;
case 'storelocation':
return StorelocationParameter::class;
case 'supplier':
return SupplierParameter::class;
case 'attachment_type':
return AttachmentTypeParameter::class;
case 'group':
return GroupParameter::class;
case 'measurement_unit':
return MeasurementUnitParameter::class;
case 'currency':
return Currency::class;
default:
throw new \InvalidArgumentException('Invalid parameter type: '.$type);
}
}
/**
* @Route("/parameters/{type}/search/{query}", name="typeahead_parameters", requirements={"type" = ".+"})
* @param string $query
* @return JsonResponse
*/
public function parameters(string $type, EntityManagerInterface $entityManager, string $query = ""): JsonResponse
{
$class = $this->typeToParameterClass($type);
/** @var ParameterRepository $repository */
$repository = $entityManager->getRepository($class);
$data = $repository->autocompleteParamName($query);
return new JsonResponse($data);
}
/**
* @Route("/tags/search/{query}", name="typeahead_tags", requirements={"query"= ".+"})
*/

View file

@ -33,7 +33,7 @@ use Symfony\Component\Validator\Constraints as Assert;
use function sprintf;
/**
* @ORM\Entity()
* @ORM\Entity(repositoryClass="App\Repository\ParameterRepository")
* @ORM\Table("parameters")
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\DiscriminatorColumn(name="type", type="smallint")

View file

@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* @ORM\Entity()
* @ORM\Entity(repositoryClass="App\Repository\ParameterRepository")
* @UniqueEntity(fields={"name", "group", "element"})
*/
class AttachmentTypeParameter extends AbstractParameter

View file

@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* @ORM\Entity()
* @ORM\Entity(repositoryClass="App\Repository\ParameterRepository")
* @UniqueEntity(fields={"name", "group", "element"})
*/
class CategoryParameter extends AbstractParameter

View file

@ -30,7 +30,7 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* A attachment attached to a category element.
*
* @ORM\Entity()
* @ORM\Entity(repositoryClass="App\Repository\ParameterRepository")
* @UniqueEntity(fields={"name", "group", "element"})
*/
class CurrencyParameter extends AbstractParameter

View file

@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* @ORM\Entity()
* @ORM\Entity(repositoryClass="App\Repository\ParameterRepository")
* @UniqueEntity(fields={"name", "group", "element"})
*/
class DeviceParameter extends AbstractParameter

View file

@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* @ORM\Entity()
* @ORM\Entity(repositoryClass="App\Repository\ParameterRepository")
* @UniqueEntity(fields={"name", "group", "element"})
*/
class FootprintParameter extends AbstractParameter

View file

@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* @ORM\Entity()
* @ORM\Entity(repositoryClass="App\Repository\ParameterRepository")
* @UniqueEntity(fields={"name", "group", "element"})
*/
class GroupParameter extends AbstractParameter

View file

@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* @ORM\Entity()
* @ORM\Entity(repositoryClass="App\Repository\ParameterRepository")
* @UniqueEntity(fields={"name", "group", "element"})
*/
class ManufacturerParameter extends AbstractParameter

View file

@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* @ORM\Entity()
* @ORM\Entity(repositoryClass="App\Repository\ParameterRepository")
* @UniqueEntity(fields={"name", "group", "element"})
*/
class MeasurementUnitParameter extends AbstractParameter

View file

@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* @ORM\Entity()
* @ORM\Entity(repositoryClass="App\Repository\ParameterRepository")
* @UniqueEntity(fields={"name", "group", "element"})
*/
class PartParameter extends AbstractParameter

View file

@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* @ORM\Entity()
* @ORM\Entity(repositoryClass="App\Repository\ParameterRepository")
* @UniqueEntity(fields={"name", "group", "element"})
*/
class StorelocationParameter extends AbstractParameter

View file

@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* @ORM\Entity()
* @ORM\Entity(repositoryClass="App\Repository\ParameterRepository")
* @UniqueEntity(fields={"name", "group", "element"})
*/
class SupplierParameter extends AbstractParameter

View file

@ -0,0 +1,33 @@
<?php
namespace App\Repository;
class ParameterRepository extends DBElementRepository
{
/**
* Find parameters using a parameter name
* @param string $name The name to search for
* @param bool $exact True, if only exact names should match. False, if the name just needs to be contained in the parameter name
* @param int $max_results
* @return array
*/
public function autocompleteParamName(string $name, bool $exact = false, int $max_results = 50): array
{
$qb = $this->createQueryBuilder('parameter');
$qb->distinct()
->select('parameter.name')
->addSelect('parameter.symbol')
->addSelect('parameter.unit')
->where('parameter.name LIKE :name');
if ($exact) {
$qb->setParameter('name', $name);
} else {
$qb->setParameter('name', '%'.$name.'%');
}
$qb->setMaxResults($max_results);
return $qb->getQuery()->getArrayResult();
}
}

View file

@ -1,117 +0,0 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\Services;
use App\Entity\Parts\Part;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use function array_slice;
/**
* A service related for searching for tags. Mostly useful for autocomplete reasons.
*/
class TagFinder
{
protected $em;
public function __construct(EntityManagerInterface $entityManager)
{
$this->em = $entityManager;
}
/**
* Search tags that begins with the certain keyword.
*
* @param string $keyword The keyword the tag must begin with
* @param array $options Some options specifying the search behavior. See configureOptions for possible options.
*
* @return string[] an array containing the tags that match the given keyword
*/
public function searchTags(string $keyword, array $options = []): array
{
$results = [];
$keyword_regex = '/^'.preg_quote($keyword, '/').'/';
$resolver = new OptionsResolver();
$this->configureOptions($resolver);
$options = $resolver->resolve($options);
//If the keyword is too short we will get to much results, which takes too much time...
if (mb_strlen($keyword) < $options['min_keyword_length']) {
return [];
}
//Build a query to get all
$qb = $this->em->createQueryBuilder();
$qb->select('p.tags')
->from(Part::class, 'p')
->where('p.tags LIKE ?1')
->setMaxResults($options['query_limit'])
//->orderBy('RAND()')
->setParameter(1, '%'.$keyword.'%');
$possible_tags = $qb->getQuery()->getArrayResult();
//Iterate over each possible tags (which are comma separated) and extract tags which match our keyword
foreach ($possible_tags as $tags) {
$tags = explode(',', $tags['tags']);
$results = array_merge($results, preg_grep($keyword_regex, $tags));
}
$results = array_unique($results);
//Limit the returned tag count to specified value.
return array_slice($results, 0, $options['return_limit']);
}
protected function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'query_limit' => 75,
'return_limit' => 75,
'min_keyword_length' => 2,
]);
}
}