Skip to content

Commit

Permalink
Algolia's facets backend rendering (algolia#802)
Browse files Browse the repository at this point in the history
  • Loading branch information
damcou authored Jul 24, 2019
1 parent 5d88ad8 commit 24886b2
Show file tree
Hide file tree
Showing 42 changed files with 2,264 additions and 40 deletions.
127 changes: 127 additions & 0 deletions Adapter/Aggregation/Builder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php

namespace Algolia\AlgoliaSearch\Adapter\Aggregation;

use Magento\Catalog\Model\ProductFactory;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Ddl\Table;
use Magento\Framework\Search\Adapter\Aggregation\AggregationResolverInterface;
use Magento\Framework\Search\Adapter\Mysql\Aggregation\Builder\Container as AggregationContainer;
use Magento\Framework\Search\Adapter\Mysql\Aggregation\DataProviderContainer;
use Magento\Framework\Search\Adapter\Mysql\TemporaryStorage;
use Magento\Framework\Search\RequestInterface;

class Builder
{
/** @var DataProviderContainer */
private $dataProviderContainer;

/** @var AggregationContainer */
private $aggregationContainer;

/** @var ResourceConnection */
private $resource;

/** @var AggregationResolverInterface */
private $aggregationResolver;

/** @var ProductFactory */
private $productFactory;

/**
* @param ResourceConnection $resource
* @param DataProviderContainer $dataProviderContainer
* @param AggregationContainer $aggregationContainer
* @param AggregationResolverInterface $aggregationResolver
* @param ProductFactory $productFactory
*/
public function __construct(
ResourceConnection $resource,
DataProviderContainer $dataProviderContainer,
AggregationContainer $aggregationContainer,
AggregationResolverInterface $aggregationResolver,
ProductFactory $productFactory
) {
$this->dataProviderContainer = $dataProviderContainer;
$this->aggregationContainer = $aggregationContainer;
$this->resource = $resource;
$this->aggregationResolver = $aggregationResolver;
$this->productFactory = $productFactory;
}

public function build(RequestInterface $request, Table $documentsTable, array $documents, array $facets)
{
return $this->processAggregations($request, $documentsTable, $documents, $facets);
}

private function processAggregations(RequestInterface $request, Table $documentsTable, $documents, $facets)
{
$aggregations = [];
$documentIds = $documents ? $this->extractDocumentIds($documents) : $this->getDocumentIds($documentsTable);
$buckets = $this->aggregationResolver->resolve($request, $documentIds);
$dataProvider = $this->dataProviderContainer->get($request->getIndex());

foreach ($buckets as $bucket) {
if (isset($facets[$bucket->getField()])) {
$aggregations[$bucket->getName()] =
$this->formatAggregation($bucket->getField(), $facets[$bucket->getField()]);
} else {
$aggregationBuilder = $this->aggregationContainer->get($bucket->getType());
$aggregations[$bucket->getName()] = $aggregationBuilder->build(
$dataProvider,
$request->getDimensions(),
$bucket,
$documentsTable
);
}
}

return $aggregations;
}

private function formatAggregation($attribute, $facetData)
{
$aggregation = [];

foreach ($facetData as $value => $count) {
$optionId = $this->getOptionIdByLabel($attribute, $value);
$aggregation[$optionId] = [
'value' => (string) $optionId,
'count' => (string) $count,
];
}

return $aggregation;
}

private function getOptionIdByLabel($attributeCode, $optionLabel)
{
$product = $this->productFactory->create();
$isAttributeExist = $product->getResource()->getAttribute($attributeCode);
$optionId = '';
if ($isAttributeExist && $isAttributeExist->usesSource()) {
$optionId = $isAttributeExist->getSource()->getOptionId($optionLabel);
}

return $optionId;
}

private function extractDocumentIds(array $documents)
{
return $documents ? array_keys($documents) : [];
}

private function getDocumentIds(Table $documentsTable)
{
$select = $this->getConnection()
->select()
->from($documentsTable->getName(), TemporaryStorage::FIELD_ENTITY_ID);

return $this->getConnection()->fetchCol($select);
}

private function getConnection()
{
return $this->resource->getConnection();
}
}
15 changes: 13 additions & 2 deletions Adapter/Algolia.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Algolia\AlgoliaSearch\Adapter;

use Algolia\AlgoliaSearch\Adapter\Aggregation\Builder as AlgoliaAggregationBuilder;
use Algolia\AlgoliaSearch\Helper\AdapterHelper;
use AlgoliaSearch\AlgoliaConnectionException;
use Magento\Framework\App\ResourceConnection;
Expand Down Expand Up @@ -38,6 +39,9 @@ class Algolia implements AdapterInterface
/** @var AdapterHelper */
private $adapterHelper;

/** @var AlgoliaAggregationBuilder */
private $algoliaAggregationBuilder;

/** @var DocumentFactory */
private $documentFactory;

Expand All @@ -53,6 +57,7 @@ class Algolia implements AdapterInterface
* @param AggregationBuilder $aggregationBuilder
* @param TemporaryStorageFactory $temporaryStorageFactory
* @param AdapterHelper $adapterHelper
* @param AlgoliaAggregationBuilder $algoliaAggregationBuilder
* @param DocumentFactory $documentFactory
*/
public function __construct(
Expand All @@ -62,6 +67,7 @@ public function __construct(
AggregationBuilder $aggregationBuilder,
TemporaryStorageFactory $temporaryStorageFactory,
AdapterHelper $adapterHelper,
AlgoliaAggregationBuilder $algoliaAggregationBuilder,
DocumentFactory $documentFactory
) {
$this->mapper = $mapper;
Expand All @@ -70,6 +76,7 @@ public function __construct(
$this->aggregationBuilder = $aggregationBuilder;
$this->temporaryStorageFactory = $temporaryStorageFactory;
$this->adapterHelper = $adapterHelper;
$this->algoliaAggregationBuilder = $algoliaAggregationBuilder;
$this->documentFactory = $documentFactory;
}

Expand All @@ -93,11 +100,12 @@ public function query(RequestInterface $request)
$documents = [];
$totalHits = 0;
$table = null;
$facetsFromAlgolia = null;

try {
// If instant search is on, do not make a search query unless SEO request is set to 'Yes'
if (!$this->adapterHelper->isInstantEnabled() || $this->adapterHelper->makeSeoRequest()) {
list($documents, $totalHits) = $this->adapterHelper->getDocumentsFromAlgolia();
list($documents, $totalHits, $facetsFromAlgolia) = $this->adapterHelper->getDocumentsFromAlgolia();
}

$apiDocuments = array_map([$this, 'getApiDocument'], $documents);
Expand All @@ -106,7 +114,8 @@ public function query(RequestInterface $request)
return $this->nativeQuery($request);
}

$aggregations = $this->aggregationBuilder->build($request, $table, $documents);
$aggregations = $this->algoliaAggregationBuilder->build($request, $table, $documents, $facetsFromAlgolia);

$response = [
'documents' => $documents,
'aggregations' => $aggregations,
Expand Down Expand Up @@ -167,6 +176,7 @@ private function getConnection()
* Get rows size
*
* @param Select $query
*
* @return int
*/
private function getSize(Select $query)
Expand All @@ -185,6 +195,7 @@ private function getSize(Select $query)
* Reset limit and offset
*
* @param Select $query
*
* @return Select
*/
private function getSelectCountSql(Select $query)
Expand Down
38 changes: 38 additions & 0 deletions Block/Navigation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Algolia\AlgoliaSearch\Block;

use Algolia\AlgoliaSearch\Helper\ConfigHelper;

class Navigation extends \Magento\LayeredNavigation\Block\Navigation
{
/** @var ConfigHelper */
private $configHelper;

/**
* Navigation constructor.
*
* @param \Magento\Framework\View\Element\Template\Context $context
* @param \Magento\Catalog\Model\Layer\Resolver $layerResolver
* @param \Magento\Catalog\Model\Layer\FilterList $filterList
* @param \Magento\Catalog\Model\Layer\AvailabilityFlagInterface $visibilityFlag
* @param ConfigHelper $configHelper
* @param array $data
*/
public function __construct(
\Magento\Framework\View\Element\Template\Context $context,
\Magento\Catalog\Model\Layer\Resolver $layerResolver,
\Magento\Catalog\Model\Layer\FilterList $filterList,
\Magento\Catalog\Model\Layer\AvailabilityFlagInterface $visibilityFlag,
ConfigHelper $configHelper,
array $data
) {
parent::__construct($context, $layerResolver, $filterList, $visibilityFlag, $data);
$this->configHelper = $configHelper;

if ($this->configHelper->isBackendRenderingEnabled()) {
$this->getLayout()->unsetElement('catalog.compare.sidebar');
$this->getLayout()->unsetElement('wishlist_sidebar');
}
}
}
40 changes: 40 additions & 0 deletions Block/Navigation/Renderer/CategoryRenderer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace Algolia\AlgoliaSearch\Block\Navigation\Renderer;

use Magento\Catalog\Model\Layer\Filter\FilterInterface;
use Magento\Framework\View\Element\Template;
use Magento\LayeredNavigation\Block\Navigation\FilterRendererInterface;

class CategoryRenderer extends Template implements FilterRendererInterface
{
/** @var string */
protected $_template = 'Algolia_AlgoliaSearch::layer/filter/category.phtml';

public function isMultipleSelectEnabled()
{
return false;
}

public function render(FilterInterface $filter)
{
$html = '';
$this->filter = $filter;

if ($this->canRenderFilter()) {
$this->assign('filterItems', $filter->getItems());
$html = $this->_toHtml();
$this->assign('filterItems', []);
}

return $html;
}

/**
* {@inheritdoc}
*/
protected function canRenderFilter()
{
return true;
}
}
Loading

0 comments on commit 24886b2

Please sign in to comment.