drupal-graphql / graphql-search-api Goto Github PK
View Code? Open in Web Editor NEWGraphQL & Drupal Search API integration.
GraphQL & Drupal Search API integration.
How do I access the search_api_relevance
score in a query? This is a field that I can expose, filter and sort by in a Drupal view via search_api
but how can I get this same data in a GraphQL query?
If it's not currently available, it would be very helpful to be able to use this field for debugging and custom sort purposes.
Thanks!
We can use tags to exclude certain conditions from being counted by facets.
The idea was to introduce the concept of facet exclusion. That is, to exclude facets from being altered by conditions of that same facet in the main query.
For example:
searchAPISearch(index_id: "anabranch_connect_index", condition_group:{
conjunction:AND,
groups: [
{
conjunction: OR,
tags:["facet:study_fields"],
conditions: [
{operator: "=", name: "study_fields", value: "Finance"},
]
},
{
conjunction: OR,
tags:["facet:article_type"],
conditions: [
{operator: "=", name: "article_type", value: "Advice"},
]
},
]
},
Need to update the docs with this.
I could manage to get search_api_graphql working as expected. Now i'm stuck with a question regarding facets parameter input.
I can query for facets and get the list of available facets back. That's fine.
{
searchAPISearch(
index_id: "search_index",
language: "de",
range: {start: 1, end: 5},
facets: [
{operator: "=", field: "field_a", limit: 1, min_count: 0, missing: false}
{operator: "=", field: "field_b", limit: 1, min_count: 0, missing: false}
]
)
{
facets {
name
values {
filter
count
}
}
documents {
... on SearchIndexDoc {
title
}
}
}
}
I get the correct result (that's also fine).
{
"data": {
"searchAPISearch": {
"result_count": 3,
"facets": [
{
"name": "field_a",
"values": [
{
"filter": "34",
"count": 3
}
]
},
{
"name": "field_a",
"values": [
{
"filter": "35",
"count": 1
}
]
},
{
"name": "field_b",
"values": [
{
"filter": "42",
"count": 2
}
]
},
{
"name": "field_b",
"values": [
{
"filter": "10",
"count": 1
}
]
}
],
"documents": [
{
"title": "Hello World 1"
},
{
"title": "Hello World 2"
}
]
}
}
}
So here is my question: How can i pass a filtered facet item to the query? So lets say i want to filter for field_a=34 and field_b=10
and get all results that matches these elements. Is that done with conditions and condition groups (https://graphql-search-api.readthedocs.io/en/latest/search-parameters/#conditions)?
Original request was opened here: https://www.drupal.org/project/graphql_search_api/issues/3183710 @Kyubbi instructed to open here on github instead of Drupal.org
Hi! I would appreciate some guidance from someone that has already been there and has the battle scars to share. AdvThanksance!!!
The module stack is pretty tall and it has been difficult to narrow down any further. Looking into the Common pitfalls and FAQs has not led to any inspiration. I thought to post now but I will look at upgrading to the latest recommended versions of the module. Previous recommendations to upgrade to search_aol_solr to 4.1 from 8.x-1.5 did not resulted in any improvement.
Givens:
Knowns:
Relevant versions and enabled modules:
Evidence 1: Custom field definition
namespace Drupal\brqc_custom\Plugin\search_api\processor;
use Drupal\search_api\Datasource\DatasourceInterface;
use Drupal\search_api\Item\ItemInterface;
use Drupal\search_api\Processor\ProcessorPluginBase;
use Drupal\search_api\Processor\ProcessorProperty;
/**
* Adds a custom field to the indexed data.
*
* @SearchApiProcessor(
* id = "group__members",
* label = @Translation("Group Members"),
* description = @Translation("Add a custom field of group members to search index."),
* stages = {
* "add_properties" = 0,
* },
* locked = true,
* hidden = false,
* )
*/
class GroupMembers extends ProcessorPluginBase {
/**
* machine name of the processor.
* @var string
*/
protected $processor_id = 'group__members';
/**
* {@inheritdoc}
*/
public function getPropertyDefinitions(DatasourceInterface $datasource = NULL) {
$properties = array();
if (!$datasource) {
$definition = array(
'label' => $this->t('Group Members'),
'description' => $this->t('Custom field for storing all members that belong to this group.'),
'type' => 'string',
'processor_id' => $this->getPluginId(),
);
$properties[$this->processor_id] = new ProcessorProperty($definition);
}
return $properties;
}
/**
* {@inheritdoc}
*/
public function addFieldValues(ItemInterface $item) {
$entity = $item->getOriginalObject()->getValue();
$entity_type = $entity->getEntityTypeId();
// Use $entity to get custom field.
// Determine the correct content type.
if ($entity_type == 'group') {
// Get members from the group. Temporarily using superhero secret identities.
$members = [
'John Doe',
'Peter Parker',
'Bruce Wayne',
'Clark Kent',
'Diana Prince',
'Arthur Curry',
'Jack Napier',
'James Howlett',
];
$temp_members = [];
$random = rand(0, count($members));
$member_keys = array_rand($members, $random);
if (!is_array($member_keys)) {
$temp_members[] = $members[$member_keys];
}
else {
foreach ($member_keys as $member_key) {
$temp_members[] = $members[$member_key];
}
}
$members = $temp_members;
// Add the group members in individually.
if (count($members) > 0) {
$fields = $this->getFieldsHelper()
->filterForPropertyPath($item->getFields(), NULL, $this->processor_id);
foreach ($fields as $field) {
foreach ($members as $member) {
$field->addValue($member);
}
}
}
}
}
}
Evidence 2: Selected portions of query results from Solr admin.
{
"response": {
"numFound": 1,
"start": 0,
"docs": [
{
… stuff deleted
"sm_group__members": [
"John Doe",
"Peter Parker",
"Bruce Wayne",
"Clark Kent",
"Diana Prince",
"Arthur Curry",
"Jack Napier",
"James Howlett"
],
… stuff deleted
}
]
}
}
Evidence 3: GraphQL Explorer results
{
"data": {
"searchAPISearch": {
"count": 1,
"facets": [
{
"name": "group__type",
"values": [
{
"count": 1,
"filter": "!"
}
]
},
{
"name": "group__members",
"values": [
{
"count": 1,
"filter": "Arthur Curry"
},
{
"count": 1,
"filter": "Bruce Wayne"
},
{
"count": 1,
"filter": "Clark Kent"
},
{
"count": 1,
"filter": "Diana Prince"
},
{
"count": 1,
"filter": "Jack Napier"
},
{
"count": 1,
"filter": "James Howlett"
},
{
"count": 1,
"filter": "John Doe"
},
{
"count": 1,
"filter": "Peter Parker"
}
]
}
],
"documents": [
{
… stuff deleted
"group__members": "John Doe",
… stuff deleted
}
]
}
}
}
How does facets work? I'm using Drupal 9 and elasticsearch with elasticsearch-connector module and everything works fine except facets. They are always empty in the results. Should I install a facet module for this? Should I create a datasource for facets? How can I get facets to work with Drupal 9 and elasticsearch-connector module ? Thanks.
I may have found a bug with using aggregated fields.
I've currently have a sitewide search I'd like to search across all selected entities (ex: Nodes and Users) So I've set up a Solr index set up with aggregated fields called "title" which includes Node "Title" field as well as a User "name" field.
First when checking in the GraphQL explorer, the aggregated fields do not appear as available fields. So I then switched my query to do fulltext search on all the full text fields available, however, this results in an internal error from PHP
Notice: Undefined variable: results in Drupal\graphql_search_api\Plugin\GraphQL\Fields\SearchAPISearch->resolveValues() (line 62 of modules/contrib/graphql_search_api/src/Plugin/GraphQL/Fields/SearchAPISearch.php).
Drupal\graphql_search_api\Plugin\GraphQL\Fields\SearchAPISearch->resolveValues(NULL, Array, Object, Object)
iterator_to_array(Object) (Line: 163)
As well as a graphql error in the drupal logs:
Call to a member function getResultItems() on null
Module isn't properly converting a "NULL" condition to a NULL in the Search API addCondition.
In order this module work with Drupal 9, it should reply on GraphQL module version 4, but not 3.
It should return at least on of these results:
as discussed with @duartegarin on the drupal/#graphql chat https://drupal.slack.com/archives/C6LMJ0ZAT/p1570093282084300
other information:
@joaogarin I just realised in the dependencies you added is search_api_solr as well.
That should be removed as this module doesn't depend on solr and can be used with any search backend.
Hi,
it seems to be a great module but it does not seem to work with the database backend.
Is it exclusively for Solr or Elastic search ?
I installed drupal graphql 4.x,
graphql search api 1.1,
search api and database search on drupal 8.8.5. I created a database search server, an index on a content type and a graphql server but I don't have the searchapisearch autocomplete in the graphiql explorer.
Anyway, thank you for this beautiful module!
There are plans to support the V4 of graphql?
We are using this cool module to expose one of our Search API indexes to our front end.
It works really cool and has saved us days of hard work :) Just now that we are wrapping up the integration with Search API, I discovered a minor bug.
graphql_search_api would show NULL in the result for any value that looks like empty, i.e. empty($value) === TRUE
(in PHP code).
The way I discovered it was the following, we had an integer field in the index and for many documents it would be 0. For such documents the graphql query would return null instead of the number.
Consider the following example. First, let me demonstrate my solr index - you will see it has the field its_kaltura_views_30d
defined for multiple documents it is 0:
$ curl 'http://solr:8983/solr/drupal/select?q=index_id:kaltura&indent=on&fl=its_kaltura_mid,its_kaltura_views_30d'
{
"response":{"numFound":6,"start":0,"docs":[
{
"its_kaltura_views_30d":15,
"its_kaltura_mid":2},
{
"its_kaltura_views_30d":2,
"its_kaltura_mid":3},
{
"its_kaltura_views_30d":0,
"its_kaltura_mid":4},
{
"its_kaltura_views_30d":0,
"its_kaltura_mid":5},
{
"its_kaltura_views_30d":0,
"its_kaltura_mid":6},
{
"its_kaltura_views_30d":0,
"its_kaltura_mid":7}]
}}
Now if I request the same data over graphql endpoint, for non-zero I will see the correct number whereas all the zeros are replaced with NULL:
Hello,
We want to sort the results by relevance but not able to do so.
We tried with different field names like "relevance", "score", "_score", "search_api_relevance" but every time getting error response.
Can any one let me know what should be the field name here to use?
Thanks in advance.
In any case if I try to use keys, as in example below, in return I get "Internal server error".
query {
searchAPISearch (
index_id:"content",
fulltext: {
keys: "who",
fields: ["title"]
}
) {
documents {
... on ContentDoc {
title
}
}
}
}
Empty string for keys doesn't throw error but also returns everything. After investigation, conditions also throws the same error
I'm trying to query for any items in an index that do not have a null string field. I have found that because the value field is required for the ConditionInput I cannot accomplish this.
I have tested making the value field in the ConditionInput optional and that does allow me to query for items where a given field is null.
Below is a working example after I modified the Drupal\graphql_search_api\Plugin\GraphQL\InputTypes\ConditionInput.php file
query{
searchAPISearch(
index_id:"default_solr_index",
conditions: [{
operator: "<>"
name: "field_my_field"
}]) {
result_count
documents {
... on DefaultSolrIndexDoc {
nid
title
field_my_field
}
}
}
}
I edited the ConditionInput.php file to look like
<?php
namespace Drupal\graphql_search_api\Plugin\GraphQL\InputTypes;
use Drupal\graphql\Plugin\GraphQL\InputTypes\InputTypePluginBase;
/**
* Condition input type.
*
* @GraphQLInputType(
* id = "condition_input",
* name = "ConditionInput",
* fields = {
* "name" = "String!",
* "value" = "String",
* "operator" = "String"
* }
* )
*/
class ConditionInput extends InputTypePluginBase {
}
Is there a different way I can query for an item without a null field value, or is this a bug?
Document latest change to these parameters.
Not sure if it's feature or a bug, but have following scenario:
But GraphQL response return Index name instead content type name. Check response image.
Hello,
Looks like conjunction inside just below Condition Groups is always taking AND, irrespective of conjunction supplied.
Main Query
query ($key: [String]!, $offset: Int!, $limit: Int!, $filters: ConditionGroupInput, $sort: [SortInput]) {
searchAPISearch(index_id: "myindex", condition_group: $filters, range: {offset: $offset, limit: $limit}, fulltext: {keys: $key}, sort: $sort) {
count: result_count
documents {
... on MyindexDoc {
group__title
content__content_title
}
}
}
}
Scenario 1 : Supplied query variables (giving expected result)
{
"offset": "0",
"limit": 50,
"key": "test",
"filters": {
"conjunction": "OR",
"groups": [
{
"conditions": [
{
"operator": "=",
"name": "content__content_type",
"value": "article"
},
{
"operator": "=",
"name": "content__field_is_application",
"value": "yes"
},
{
"operator": "<>",
"name": "content__field_life_cycle__entity__name",
"value": "Retired"
}
]
}
]
},
"sort": [
{
"field": "search_api_relevance",
"value": "desc"
}
]
}
Scenario 2 : Supplied query variables (giving expected result)
{
"offset": "0",
"limit": 50,
"key": "test",
"filters": {
"conjunction": "OR",
"groups": [
{
"conditions": [
{
"operator": ">",
"name": "group__id",
"value": "0"
},
{
"operator": "<>",
"name": "group__field_life_cycle__entity__title",
"value": "Retired"
},
{
"operator": "<>",
"name": "group__field_is_live",
"value": "0"
}
]
}
]
},
"sort": [
{
"field": "search_api_relevance",
"value": "desc"
}
]
}
Scenario 3 : Supplied query variables (giving 0 result, but it should be result of Scenario 1 + Scenario 2)
{
"offset": "0",
"limit": 50,
"key": "test",
"filters": {
"conjunction": "OR",
"groups": [
{
"conditions": [
{
"operator": "=",
"name": "content__content_type",
"value": "article"
},
{
"operator": "=",
"name": "content__field_is_application",
"value": "yes"
},
{
"operator": "<>",
"name": "content__field_life_cycle__entity__name",
"value": "Retired"
}
]
},
{
"conditions": [
{
"operator": ">",
"name": "group__id",
"value": "0"
},
{
"operator": "<>",
"name": "group__field_life_cycle__entity__title",
"value": "Retired"
},
{
"operator": "<>",
"name": "group__field_is_live",
"value": "0"
}
]
}
]
},
"sort": [
{
"field": "search_api_relevance",
"value": "desc"
}
]
}
What I assume conjunction just above groups always using AND operation between groups objects, but I want to use OR here.
Am I doing something wrong?
If $results errors or is NULL:
public function resolveValues($value, array $args, ResolveContext $context, ResolveInfo $info) {
// Load up the index passed in argument.
$this->index = $this->entityTypeManager->getStorage('search_api_index')->load($args['index_id']);
// Prepare the query with our arguments.
$this->prepareSearchQuery($args);
// Execute search.
try {
$results = $this->query->execute();
}
// Handle error, check exception type -> SearchApiException ?
catch (\Exception $exception) {
$this->logger->get('graphql_search_api')->error($exception->getMessage());
}
// Get search response from results.
$search_response = $this->getSearchResponse($results);
// Add the result count to the response.
$search_response['result_count'] = $results->getResultCount();
// Set response type.
$search_response['type'] = 'SearchAPIResult';
yield $search_response;
}
I get a PHP notice:
"Notice: Undefined variable: results in Drupal\graphql_search_api\Plugin\GraphQL\Fields\SearchAPISearch->resolveValues() (line 97 of modules/contrib/graphql_search_api/src/Plugin/GraphQL/Fields/SearchAPISearch.php)."
In the browser I get a JS 500 error about an unexpected token.
This turns out to be (a rather common case) if you are running Solr and the Solr results are stale and return items that aren't in Drupal (like say, syncing a prod database to dev) and Drupal cannot load the item to display in a result. This is a little hard to track down if you aren't familiar with Search API and Solr.
I think the 3 lines pertaining to $search_response should be moved into the try block, and the catch block should have:
$search_response = [];
$search_response['result_count'] = 0;
$search_response['type'] = 'SearchAPIResult';
This way at least the client doesn't break down and "empty results" message configured with Search will work.
I am facing an issue with two indexes (on two solr cores). When having the same fields e.g. nid
with an identical machine name in both indexes, this field can only be retrieved for one index.
Lets assume i have 2 indexes: "Index A" and "Index B" and add this field:
My query looks like this and works, as expected:
{
searchAPISearch(
index_id: "index_a",
language: "de",
range: {start: 1, end: 50}
) {
result_count
documents {
... on IndexADoc {
nid
}
}
}
}
For the query to Index B i get "Cannot query field \"nid\" on type \"IndexBDoc\"
. The schema inspector doesn't show them (only fields that are not in Index A are shown).
{
searchAPISearch(
index_id: "index_b",
language: "de",
range: {start: 1, end: 50}
) {
result_count
documents {
... on IndexBDoc {
nid
}
}
}
}
Renaming the machine name to nid_1
and modifying the query accordingly works as a workaround.
(Please not, that the queries above are "constructed" manually and not copied 1:1 from my client project. So they might contain an error.)
@joaogarin When we added range delimiters, we assumed that start equals offset (correct) and end equals the end of the range, when in fact it is a "limit".
This means that if I want results on page 2 (on a 5 records per page) I should specify "start" as 5 and "end" as 5.
I propose we refactor this to "offset" and "limit" to be consistent with the rest of GraphQL module implementations.
However, this is a breaking change given that all consumers need to update their implementations.
Make this module compatible with drupal graphql v4 schema
I am using the language specification in my query just fine and the results are as expected. However, the facets still return items in all languages. Is there a way to adhere the language specification to the facets?
My query
query {
searchAPISearch(
index_id: "content"
range: {offset: 0, limit: 25}
language: "en"
facets: [
{operator: "=", field: "product", limit: 0, min_count: 0, missing: false}
]
) {
result_count
documents {
... on ContentDoc {
nid
url
title
product
type
version
langcode
}
}
facets {
name
values {
count
name: filter
}
}
}
}
Caching is vital -- but when working with a very large or otherwise busy Search API index, the Search API tracking can report a successfully indexed document to GraphQL Search API while it's not actually been indexed in solr (or whatever backend) yet before the client has made the query following having entered new records.
In our application, we found it helpful to artificially reduce cache max-age
to allow more opportunity for the indexer to present the freshest data as soon as it is available.
I am working on a patch for this now.
Upgrade status reports only one warning:
Add core_version_requirement: ^8 || ^9 to designate that the module is compatible with Drupal 9. See https://drupal.org/node/3070687.
Please, review #36 to get the module available on D9 too.
Hey @duartegarin do you see a problem in making a new release with b7a8811 ? Maybe an Rc2.
Got it ready to go, just want to make sure you'r ok with it.
If the query contains a condition group argument with no groups inside, the foreach
loop in addConditionGroup()
may emit a warning/notice.
I have the search highlight processor enabled but I don't see a way to access it through this API and I saw no mention of it anywhere. Is this something I have to do manually by going through each field which seems very tedious or is there a way to access this?
Hopefully this is not user error; however, it seems that condition groups are not properly applying for any fields. I have the following query that is supposed to only results whose 'field_archive_date' is NULL OR greater than or equal to a certain date:
{
allResults: searchAPISearch(
index_id: "sitewide"
range: {start: 0, end: 12}
condition_group: {
conjunction: OR,
conditions: [
{name: "field_archive_date", value: "NULL", operator: "="}
{name: "field_archive_date", value: "2019-03-18", operator: ">="}
]
}
sort: {field: "created" value: "desc"}
facets: [
{operator: "=", field: "product", limit: 0, min_count: 0, missing: false}
{operator: "=", field: "resource_type", limit: 0, min_count: 0, missing: false}
{operator: "=", field: "segement", limit: 0, min_count: 0, missing: false}
{operator: "=", field: "solution", limit: 0, min_count: 0, missing: false}
]
) {
result_count
documents {
... on SitewideDoc {
nid
field_archive_date
}
}
facets{
name
values{
count
name: filter
}
}
}
}
Unfortunately, the conditions, when applied in a condition group, are not taking effect. When they are independently applied outside of a condition group, they do apply properly. I've tested this with numerous fields, including default fields like node title, and the conditions do not seem to apply properly in that scenario as well.
I got a question, maybe you have a better understanding than me at this point about plugins. I want to override a part of our logic on getting facets back to the user in the response. We are indexing term id's in Solr, and as the module just returns whats currently indexed we only get back term id's, thats not terribly useful.
One thing that came to my mind, is overriding (altering) a certain part of the plugin for example in https://github.com/drupal-graphql/graphql-search-api/blob/8.x-1.x/src/Plugin/GraphQL/Fields/SearchAPISearch.php#L326 where I would basically do a taxonomy term load multiple to get the facets information directly from the query. maybe where we would provide a good old alter hook (? is this still a thing?)
Could also possibly do it here possibbly : https://github.com/drupal-graphql/graphql-search-api/blob/8.x-1.x/src/Plugin/GraphQL/Fields/SearchAPIFacetValues.php#L20 where I maybe would override the whole plugin?
this would allow users to extend our plugins and make their own logic..thinking what should be the module's approach to that in a higher level.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.