Creating or updating fields to Coveo is difficult

Created on 20 September 2023, 9 months ago
Updated 1 January 2024, 6 months ago

Problem/Motivation

API tokens can be scoped to allow access to create or update Fields in Coveo. Typically this would happen when adding fields to the Drupal Search API index that would then be synced in the Coveo source.

The problematic part is that there is not much checking of if a field exists first before creating it. At the same time, there should be some warnings on the field page (in search api) that updating fields that have the same name as Coveo fields can have unintended consequences - such as changing the machine name could break existing Coveo connections on disparate platforms using sources. That is because the Fields in Coveo are shared globally across all sources.

Proposed resolution

Not entirely sure at the moment. At a minimum, adding a new field to the index in Drupal should check if the field exists in Coveo first before trying to insert it with a /create command. Calling /update instead could potentially break an existing field in Coveo (I think I accidentally did this in my testing today).

πŸ“Œ Task
Status

Fixed

Version

1.0

Component

Code

Created by

πŸ‡ΊπŸ‡ΈUnited States kevinquillen

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @kevinquillen
  • πŸ‡ΊπŸ‡ΈUnited States kevinquillen

    This is the first rough pass to getting this unblocked. Rough and by no means pretty. I modified the code to do the following:

    • Per Drupal field in the index, call the API and check if it exists in Coveo. Unfortunately, the field search API is 'loose' and not strict, so we have to loop every result returned
    • If the field does not exist and is not a system field, proceed normally and add it to the 'to be created' array
    • If the field does exist, add it to the 'to be updated' array
      /**
       * {@inheritdoc}
       */
      public function updateIndex(IndexInterface $index) {
        /** @var \Drupal\search_api\Item\FieldInterface[] $fields*/
        $fields = $index->getFields(TRUE);
    
        $fields_to_add = [];
        $fields_to_update = [];
    
        $this->connect();
    
        // todo: the prefix of the fields needs to be specified in a config file.
    
        foreach ($fields as $field_name => $field) {
          $exists = $this->coveoClient->listFields($field_name, 'USER');
    
          foreach ($exists as $key => $item) {
            if ($item['name'] !== $field_name) {
              continue;
            }
    
            $coveo_field = $item;
          }
    
          // skip fields title(a coveo system field) and render_item, the html content is not a field in coveo.
          // todo: create helper to check if is system/internal coveo field.
          if ($field_name === self::CONTENT_FIELD_ID ||
            $field_name === self::TITLE) {
            continue;
          }
    
          if (!empty($coveo_field) && $coveo_field['system'] === TRUE) {
            continue;
          }
    
          $description = $field->getLabel();
          $coveo_type = $this->mapCoveoType($field->getType());
    
          // @see https://docs.coveo.com/en/8/cloud-v2-api-reference/field-api#operation/createFieldUsingPOST_2
          $field_info = [
            'name' => $field_name,
            'description' => $description,
            'type' => $coveo_type,
          ];
    
          if (!empty($coveo_field)) {
            $fields_to_update[$field_name] = $field_info;
          } else {
            $fields_to_add[$field_name] = $field_info;
          }
    
        }
    
        if ($fields_to_add) {
          $fields_created = $this->coveoClient->createFields(array_values($fields_to_add));
          // Check fields are created in coveo.
          if ($fields_created) {
            $t_args = ['%field_ids' => implode(", ", array_keys($fields_to_add))];
            \Drupal::messenger()->addStatus($this->t('New fields added in coveo: %field_ids.', $t_args));
            // watchdog log the message.
            $this->logMessage('New fields added in coveo: %field_ids.', $t_args);
          }
        }
    
        if ($fields_to_update) {
          $fields_updated = $this->coveoClient->updateFields(array_values($fields_to_update));
          // Check fields are created in coveo.
          if ($fields_updated) {
            $t_args = ['%field_ids' => implode(", ", array_keys($fields_to_update))];
            \Drupal::messenger()->addStatus($this->t('Fields updated in coveo: %field_ids.', $t_args));
            // watchdog log the message.
            $this->logMessage('Fields updated in coveo: %field_ids.', $t_args);
          }
        }
      }
    

    This caused a brand new field to appear in Coveo (without using the Coveo admin), which is what we want:

    When the other issue is fixed about using the correct API URLs according to the connection settings, the "Configure" link will jump right to the Coveo admin to change flags on the field (facet, sortable, etc).

  • πŸ‡ΊπŸ‡ΈUnited States kevinquillen

    Some small tweaks.

    
        // todo: the prefix of the fields needs to be specified in a config file.
        foreach ($fields as $field_name => $field) {
          $coveo_field = NULL;
          $exists = $this->coveoClient->listFields($field_name, 'USER');
    
          foreach ($exists as $item) {
            if ($item['name'] !== $field_name) {
              continue;
            }
    
            $coveo_field = $item;
          }
    
          // skip fields title(a coveo system field) and render_item, the html content is not a field in coveo.
          // todo: create helper to check if is system/internal coveo field.
          if ($field_name === self::TITLE) {
            continue;
          }
    
          if (!empty($coveo_field) && $coveo_field['system'] === TRUE) {
            continue;
          }
    
          $description = $field->getLabel();
          $coveo_type = $this->mapCoveoType($field->getType());
    
          $field_info = [
            'name' => $field_name,
            'description' => $description,
            'type' => $coveo_type,
          ];
    
          if (!empty($coveo_field)) {
            $fields_to_update[$field_name] = $field_info;
          } else {
            $fields_to_add[$field_name] = $field_info;
          }
    
        }
    

    I have been able to add any number of fields from Drupal and see them appear in the Coveo source, even 'rendered item'. Not sure why that was blocked, but it works just fine. I can index it and use it for ranking along with a number of others.

  • First commit to issue fork.
  • Assigned to FatherShawn
  • πŸ‡ΊπŸ‡ΈUnited States FatherShawn New York
  • Status changed to Needs review 7 months ago
  • πŸ‡ΊπŸ‡ΈUnited States FatherShawn New York

    I've improved this for type safety and readability. Passed manual testing.

  • Merge request !13Issue 3388680 β†’ (Merged) created by FatherShawn
  • πŸ‡ΊπŸ‡ΈUnited States FatherShawn New York
  • πŸ‡ΊπŸ‡ΈUnited States FatherShawn New York
  • Issue was unassigned.
  • Status changed to Fixed 7 months ago
  • πŸ‡ΊπŸ‡ΈUnited States FatherShawn New York
  • Automatically closed - issue fixed for 2 weeks with no activity.

Production build 0.69.0 2024