Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UHF-11002 #885

Merged
merged 4 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ field_mapper_config:
value: '$._source.name[0]'
tid:
value: '$._source.tid[0]'
location:
value: '$._source.field_location'
storage_client_id: helfi_news_neighbourhoods
storage_client_config: { }
persistent_cache_max_age: 86400
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,11 @@ function helfi_paragraphs_news_list_update_9009() : void {
\Drupal::service('helfi_platform_config.config_update_helper')
->update('helfi_paragraphs_news_list');
}

/**
* UHF-11002: Update news list external entities.
*/
function helfi_paragraphs_news_list_update_9010() : void {
\Drupal::service('helfi_platform_config.config_update_helper')
->update('helfi_paragraphs_news_list');
}
114 changes: 85 additions & 29 deletions modules/helfi_paragraphs_news_list/src/ElasticExternalEntityBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ protected function request(
array $parameters,
) : array {
try {
return $this->client->search($parameters)->asArray();
return $this->client->search($parameters)?->asArray() ?? [];
}
catch (ElasticsearchException | TransportException $e) {
Error::logException($this->logger, $e);
Expand Down Expand Up @@ -135,6 +135,78 @@ protected function getFieldMapping(string $field) : string {
return $field;
}

/**
* Get callback that builds elasticsearch query fragment for given operator.
*
* @param ?string $op
* Query operation.
*
* @return callable
* Handler.
*/
protected function getOperatorCallback(?string $op): callable {
return match($op) {
'IN' => static function (array $value, string $fieldName) : array {
$inGroup = [];
foreach ($value as $v) {
$inGroup[] = ['term' => [$fieldName => $v]];
}
return [
'query' => [
'bool' => [
'must' => [
['bool' => ['should' => $inGroup]],
],
],
],
];
},
'CONTAINS' => static function (string $value, string $fieldName) : array {
return [
'query' => [
'bool' => [
'must' => [
[
'regexp' => [
$fieldName => [
'value' => $value . '.*',
'case_insensitive' => TRUE,
],
],
],
],
],
],
];
},
'GEO_DISTANCE_SORT' => static function (array $value, string $fieldName) : array {
[$coordinates, $options] = $value;

return [
'sort' => [
[
'_geo_distance' => [
$fieldName => $coordinates,
...$options,
],
],
],
];
},
default => static function (string|int|null $value, string $fieldName) : array {
return [
'query' => [
'bool' => [
'must' => [
['term' => [$fieldName => $value]],
],
],
],
];
},
};
}

/**
* Builds the elastic query for given parameters.
*
Expand All @@ -147,7 +219,10 @@ protected function getFieldMapping(string $field) : string {
* The query.
*/
protected function buildQuery(array $parameters, array $sorts) : array {
$query = [];
$body = [
'sort' => [],
'query' => [],
];

foreach ($parameters as $parameter) {
['field' => $field, 'value' => $value, 'operator' => $op] = $parameter;
Expand All @@ -156,29 +231,9 @@ protected function buildQuery(array $parameters, array $sorts) : array {
if (!$value) {
continue;
}
$callback = match($op) {
'IN' => function (array $value, string $fieldName) : array {
$inGroup = [];
foreach ($value as $v) {
$inGroup[] = ['term' => [$fieldName => $v]];
}
return ['bool' => ['should' => $inGroup]];
},
'CONTAINS' => function (string $value, string $fieldName) : array {
return [
'regexp' => [
$fieldName => [
'value' => $value . '.*',
'case_insensitive' => TRUE,
],
],
];
},
default => function (string|int|null $value, string $fieldName) : array {
return ['term' => [$fieldName => $value]];
},
};
$query['bool']['must'][] = $callback($value, $fieldName);

$callback = $this->getOperatorCallback($op);
$body = array_merge_recursive($body, $callback($value, $fieldName));
}

$sortQuery = [];
Expand All @@ -189,12 +244,13 @@ protected function buildQuery(array $parameters, array $sorts) : array {
$sortQuery[$fieldName] = ['order' => strtolower($direction)];
}

$body = array_merge_recursive($body, [
'sort' => $sortQuery,
]);

return [
'index' => $this->index,
'body' => [
'sort' => $sortQuery,
'query' => $query,
],
'body' => $body,
];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,14 @@ final class NewsNeighbourhoods extends TermBase {
*/
protected string $vid = 'news_neighbourhoods';

/**
* {@inheritdoc}
*/
protected function getFieldMapping(string $field) : string {
return match($field) {
'location' => 'field_location',
default => parent::getFieldMapping($field),
};
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

namespace Drupal\Tests\helfi_paragraphs_news_list\Kernel\ExternalEntityStorage;

use Drupal\external_entities\Entity\Query\External\Query;
use Elastic\Elasticsearch\Client;

/**
* Tests news tags storage client.
*
Expand All @@ -25,4 +28,70 @@ protected function getVid(): string {
return 'news_neighbourhoods';
}

/**
* Tests geo_distance sorting.
*/
public function testGeoDistanceQuery(): void {
$client = $this->prophesize(Client::class);
// Test geo distance sort.
$client->search([
'index' => 'news_terms',
'body' => [
'sort' => [
[
'_geo_distance' => [
'field_location' => [
'lat' => 48.8584,
'lon' => 2.2945,
],
'unit' => 'km',
'order' => 'asc',
'distance_type' => 'plane',
'mode' => 'min',
'ignore_unmapped' => FALSE,
],
],
],
'query' => [
'bool' => [
'must' => [
['term' => ['vid' => $this->getVid()]],
],
],
],
],
])
->shouldBeCalled()
->willReturn($this->createElasticsearchResponse([]));

$query = $this->getSut($client->reveal())
->getQuery();

$this->assertInstanceOf(Query::class, $query);

// Drupal query interface is not quite flexible enough to support all the
// options and parameters geo_distance sort needs, so the implementation
// uses setParameter from external_entities Query class.
$query->setParameter('location', [
[
'lat' => 48.8584,
'lon' => 2.2945,
],
[
'unit' => 'km',
// Geo distance sort direction.
'order' => 'asc',
// 'arc' is more accurate, but within
// a city it should not matter.
'distance_type' => 'plane',
// What to do in case a field has several geo points.
'mode' => 'min',
// Unmapped field cause the search to fail.
'ignore_unmapped' => FALSE,
],
], 'GEO_DISTANCE_SORT');

$query->accessCheck(FALSE)->execute();
}

}
Loading