πŸ‡ΊπŸ‡ΈUnited States @ebeyrent

Account created on 20 May 2005, over 19 years ago
#

Recent comments

πŸ‡ΊπŸ‡ΈUnited States ebeyrent

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

πŸ‡ΊπŸ‡ΈUnited States ebeyrent

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.

πŸ‡ΊπŸ‡ΈUnited States ebeyrent

I encountered this using "drush updb"

πŸ‡ΊπŸ‡ΈUnited States ebeyrent

Also looking for recommendations for a replacement.

πŸ‡ΊπŸ‡ΈUnited States ebeyrent

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.

πŸ‡ΊπŸ‡ΈUnited States ebeyrent

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.",
πŸ‡ΊπŸ‡ΈUnited States 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);
  }

}
πŸ‡ΊπŸ‡ΈUnited States ebeyrent

+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.

πŸ‡ΊπŸ‡ΈUnited States ebeyrent

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!

Production build 0.71.5 2024