ebeyrent β created an issue.
I get similar errors when my custom entities don't provide a DELETE route (our business practice is to never allow DELETE requests). I got around this by bulletproofing all the places where $target_resource_type is null
Ah, so the update hook needs to change that config setting before trying to make changes to the schema, and then set the config back when done.
I encountered this using "drush updb"
ebeyrent β created an issue.
Also looking for recommendations for a replacement.
I've applied the patch, and it fixes the issue of not being able to filter by target_type. It does not fix the inability to include the referenced data.
I have a custom entity with a DER field that allows either users or nodes to be referenced. Here's an example jsonapi representation of that field:
"subject_entity": {
"data": {
"type": "user--user",
"id": "02873322-eb7c-4984-bf32-7cb7e8ec1b68",
"meta": {
"target_type": "user",
"drupal_internal__target_id": 1237
}
},
"links": {
"self": {
"href": "https://my-site.com/jsonapi/my_entity/my_entity/cfbb318c-dc03-4061-b0a2-b123ac904a5c/relationships/subject_entity?resourceVersion=id%3A387"
}
}
}
Because this field can reference either users or nodes, I want to be able to filter by the target_type:
jsonapi/my_entity/my_entity?filter[subject_entity.meta.drupal_internal__target_id]=1237&filter[subject_entity.meta.target_type]=user
This fails:
"errors": [
{
"title": "Bad Request",
"status": "400",
"detail": "Invalid nested filtering. The field `target_type`, given in the path `subject_entity.meta.target_type`, does not exist.",
Worse, if I try to include that object in my query like this:
/jsonapi/my_entity/my_entity?include=subject_entity
I get this error:
"errors": [
{
"title": "Bad Request",
"status": "400",
"detail": "`subject_entity` is not a valid relationship field name. Possible values: revision_user, subject_entity, related_entity.",
volkswagenchick β credited ebeyrent β .
Here's how I did it.
1. Implement some hooks:
function my_module_search_api_field_type_mapping_alter(array &$mapping) {
$mapping['completion'] = 'completion';
}
function my_module_elasticsearch_connector_supported_data_types_alter(array &$data_types) {
$data_types[] = 'completion';
}
2. Add a new search_api data type:
namespace Drupal\my_module\Plugin\search_api\data_type;
use Drupal\search_api\DataType\DataTypePluginBase;
/**
* Provides a completion data type.
*
* @SearchApiDataType(
* id = "completion",
* label = @Translation("Autocomplete (suggest)"),
* description = @Translation("Indexes field using the index's suggest analyzer"),
* fallback_type = "object"
* )
*/
class CompletionDataType extends DataTypePluginBase {
/**
* {@inheritdoc}
*/
public function getValue($value) {
// Implement your logic here.
}
}
3. Subscribe to the indexing events:
namespace Drupal\my_module\EventSubscriber;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManager;
use Drupal\elasticsearch_connector\Event\BuildIndexParamsEvent;
use Drupal\elasticsearch_connector\Event\PrepareIndexMappingEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Class ElasticSearchIndexSubscriber.
*
* This subscriber gets called by the ElasticSearch Connector's IndexFactory
* class.
*
* @see \Drupal\elasticsearch_connector\ElasticSearch\Parameters\Factory\IndexFactory::mapping()
*/
class ElasticSearchIndexSubscriber implements EventSubscriberInterface {
/**
* Entity type manager.
*
* @var \Drupal\Core\Entity\Entity
*/
private $entityTypeManager;
/**
* Connection service.
*
* @var \Drupal\Core\Database\Connection
*/
private $connection;
/**
* Constructs a new ElasticSearchIndexSubscriber object.
*
* @param \Drupal\Core\Entity\EntityTypeManager $entityTypeManager
* Entity type manager.
* @param \Drupal\Core\Database\Connection $connection
* The connection service.
*/
public function __construct(EntityTypeManager $entityTypeManager, Connection $connection) {
$this->entityTypeManager = $entityTypeManager;
$this->connection = $connection;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
// Map methods in this class to defined events provided by the
// Elasticsearch Connector module.
return [
PrepareIndexMappingEvent::PREPARE_INDEX_MAPPING => ['doPrepareIndexMapping'],
BuildIndexParamsEvent::BUILD_PARAMS => ['alterIndexParams'],
];
}
/**
* Event listener.
*
* Called on elasticsearch_connector.build_params event. This method
* modifies the data to be indexed in ElasticSearch. Specifically, this adds
* handling for completion fields where the bulkIndex() method in
* \Drupal\elasticsearch_connector\ElasticSearch\Parameters\Factory\IndexFactory
* makes assumptions about how the fields' data should be represented. In
* the case of completion fields, the values are wrapped in an extra array,
* which causes indexing to fail. This method removes the array nesting so
* that the data is correct.
*
* @param \Drupal\elasticsearch_connector\Event\BuildIndexParamsEvent $event
* The event.
*
* @see \Drupal\elasticsearch_connector\ElasticSearch\Parameters\Factory\IndexFactory::bulkIndex()
*/
public function alterIndexParams(BuildIndexParamsEvent $event) {
// Add your fields here.
$completion_fields = [
'multi_field_suggest',
];
$data = $event->getElasticIndexParams();
foreach ($data['body'] as $key => $param) {
foreach ($completion_fields as $completion_field) {
if (isset($param[$completion_field])) {
$data['body'][$key][$completion_field] = array_shift($param[$completion_field]);
}
}
}
$event->setElasticIndexParams($data);
}
/**
* Event listener.
*
* Called on elasticsearch_connector.prepare_index_mapping event. This method
* sets the field type as "completion" on fields that are defined as
* completion fields in Search API.
*
* @param \Drupal\elasticsearch_connector\Event\PrepareIndexMappingEvent $event
* The event.
*
* @see \Drupal\elasticsearch_connector\ElasticSearch\Parameters\Factory\MappingFactory::mappingFromField()
* @see chemdb_search_setup_search_api_field_type_mapping_alter()
*/
public function doPrepareIndexMapping(PrepareIndexMappingEvent $event) {
$index = $this->loadIndexFromIndexName($event->getIndexName());
$params = $event->getIndexMappingParams();
foreach ($index->getFields() as $field_id => $field_data) {
if ($field_data->getType() === 'completion') {
$params['body']['properties'][$field_id]['type'] = 'completion';
// Use the standard analyzer instead of the default simply analyzer.
// @link https://www.elastic.co/guide/en/elasticsearch/reference/6.8/analysis-standard-analyzer.html
$params['body']['properties'][$field_id]['analyzer'] = 'standard';
}
}
$event->setIndexMappingParams($params);
}
/**
* Calculates the Index entity id form the event.
*
* @param string $index_name
* The long index name as a string.
*
* @return string
* The id of the associated index entity.
*/
private function getIndexIdFromIndexName($index_name):string {
$last_underscore = strrpos($index_name, '_');
if ($last_underscore) {
$index_name = substr($index_name, $last_underscore + 1);
}
return $index_name;
}
/**
* Loads the index entity associated with this event.
*
* @param string $index_name
* The long index name as a string.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* The loaded index or NULL.
*/
private function loadIndexFromIndexName($index_name):?EntityInterface {
$index_id = $this->getIndexIdFromIndexName($index_name);
$index_storage = $this->entityTypeManager->getStorage('search_api_index');
return $index_storage->load($index_id);
}
}
+1 for the patch in #7. I tested against Elastic 8.14.3 and it works.
Without this patch, I wasn't able to create the field mappings, so the sooner this gets committed, the better.
Patch looks sane to me
Hi Lee!
I am recovering from surgery, so I apologize for the delay in getting back to you. Yes, I enthusiastically support you joining the maintainers on this project, and I'm grateful for your offer to help port this module to D9!
Welcome aboard!