Building queries in ruflin Elastic search Symfony3 - elasticsearch

I am having hard times with Elastica and ruflin library for PHP to build the queries. There are no many examples I could follow.
I have User entity in Symfony project which consists of the following fields:
firstName, lastName, emailAddress, studio and member or client (depended on what user it is)
So now I would like to filter out some results.
I want to display users only matching the certain studio:
$finder = $this->get('fos_elastica.finder.app.user');
$boolQuery = new BoolQuery();
$fieldQuery = new Match();
$fieldQuery->setFieldQuery('studio', 'StackOverflow studio');
$boolQuery->addMust($fieldQuery);
I want to display all users with name John:
$fieldQuery = new MoreLikeThis();
$fieldQuery->setFields(['firstName', 'lastName', 'emailAddress']);
$fieldQuery->setLikeText('John');
$boolQuery->addMust($fieldQuery);
I want to get members of that studio (not clients)
Note: User has relation to Client and Member entity. Depending on that, it will show either Member:Object or Client:Object. In this case I want to be sure that Member exists.
$fieldQuery = new Filtered();
$fieldQuery->setFilter(new Exists('member'));
$boolQuery->addMust($fieldQuery);
$users = $finder->find($boolQuery);
The problem is that... it does not work. It shows me 0 results. No matter how I play with that it shows me 0 results or wrong results.
Can someone help me out how to build proper query using ruflin library for Elastica to get results based in the conditions I have mentioned above?
RAW QUERY:
{
"query":{
"bool":{
"must":[
{
"match":{
"studio":{
"query":"StackOverflow studio"
}
}
},
{
"filtered":{
"filter":{
"exists":{
"field":"member"
}
}
}
},
{
"more_like_this":{
"fields":[
"firstName",
"lastName",
"emailAddress"
],
"like_text":"John"
}
}
]
}
}
}
UPDATE 1:
I have manged to fix 3rd query (Exists('member')). It turned out that I have forgot to put client and member under mappings in config.yml. Still 2nd query fails.

Since there's no answer, I will answer myself.
Instead of using MoreLikeThis query to find a piece of text, I have used QueryString and thing started to work
Here's how whole thing looks like now:
config.yml:
fos_elastica:
...
indexes:
app:
types:
user:
mappings:
emailAddress: ~
firstName: ~
lastName: ~
studio: ~
member: ~
client: ~
persistence:
driver: orm
model: AppBundle\Entity\User
provider: ~
listener:
finder: ~
Controller:
$finder = $this->get('fos_elastica.finder.app.user');
$boolQuery = new BoolQuery();
$fieldQuery = new Match();
$fieldQuery->setFieldQuery('studio', 'StackOverflow studio');
$boolQuery->addMust($fieldQuery);
$fieldQuery = new Filtered();
$fieldQuery->setFilter(new Exists('member'));
$boolQuery->addMust($fieldQuery);
$fieldQuery = new QueryString();
$fieldQuery->setFields(['firstName', 'lastName', 'emailAddress']);
$fieldQuery->setQuery('John*'); // * wildcard will filter look a like results for you.
$boolQuery->addMust($fieldQuery);
$query = new Query();
$query->setQuery($boolQuery);
$query->addSort(['firstName' => 'asc']);
$users = $finder->find($query);

Related

What is the recommended schema for paginated GraphQL results

Let's say I have users list to be returned. What would be best schema strategy among following.
Users returned contains only the data of user as follows, separate query is used for pagination details. In this query the downside is we need to pass same filters to both users and usersCount query.
query {
users(skip: 0, limit: 100, filters: someFilter) {
name
},
usersCount(filters: someFilters)
}
Which return following
{
results: {
users: [
{ name: "Foo" },
{ name: "Bar" },
],
usersCount: 1000,
}
}
In this strategy we make pagination details as part of users query, we don't need to pass filters twice. I feel this query is not nice to read.
query {
users(skip: 0, limit: 100, filters: someFilter) {
items: {
name
},
count
}
}
Which returns the following result
{
results: {
users: {
items: [
{ name: "Foo" },
{ name: "Bar" },
],
count: 1000,
}
}
}
I am curious to know which strategy is the recommended way while designing paginated results?
I would recommend to follow the official recommendation on graphql spec,
You need to switch to cursor based pagination.
This type of pagination uses a record or a pointer to a record in the dataset to paginate results. The cursor will refer to a record in the database.
You can follow the example in the link.
GraphQL Cursor Connections Specification
Also checkout how GitHub does it here: https://docs.github.com/en/graphql/reference/interfaces#node

Strapi GraphQL search by multiple attributes

I've got a very simple Nuxt app with Strapi GraphQL backend that I'm trying to use and learn more about GraphQL in the process.
One of my last features is to implement a search feature where a user enters a search query, and Strapi/GraphQL performs that search based on attributes such as image name and tag names that are associated with that image. I've been reading the Strapi documentation and there's a segment about performing a search.
So in my schema.graphql, I've added this line:
type Query {
...other generated queries
searchImages(searchQuery: String): [Image
}
Then in the /api/image/config/schema.graphql.js file, I've added this:
module.exports = {
query: `
searchImages(searchQuery: String): [Image]
`,
resolver: {
Query: {
searchImages: {
resolverOf: 'Image.find',
async resolver(_, { searchQuery }) {
if (searchQuery) {
const params = {
name_contains: searchQuery,
// tags_contains: searchQuery,
// location_contains: searchQuery,
}
const searchResults = await strapi.services.image.search(params);
console.log('searchResults: ', searchResults);
return searchResults;
}
}
}
},
},
};
At this point I'm just trying to return results in the GraphQL playground, however when I run something simple in the Playground like:
query($searchQuery: String!) {
searchImages(searchQuery:$searchQuery) {
id
name
}
}
I get the error: "TypeError: Cannot read property 'split' of undefined".
Any ideas what might be going on here?
UPDATE:
For now, I'm using deep filtering instead of the search like so:
query($searchQuery: String) {
images(
where: {
tags: { title_contains: $searchQuery }
name_contains: $searchQuery
}
) {
id
name
slug
src {
url
formats
}
}
}
This is not ideal because it's not an OR/WHERE operator, meaning it's not searching by tag title or image name. It seems to only hit the first where. Ideally I would like to use Strapi's search service.
I actually ran into this problem not to recently and took a different solution.
the where condition can be combined with using either _and or _or. as seen below.
_or
articles(where: {
_or: [
{ content_contains: $dataContains },
{ description_contains: $dataContains }
]})
_and
(where: {
_and: [
{slug_contains: $categoriesContains}
]})
Additionally, these operators can be combined given that where in this instance is an object.
For your solution I would presume you want an or condition in your where filter predicate like below
images(where: {
_or: [
{ title_contains: $searchQuery },
{ name_contains: $searchQuery }
]})
Lastly, you can perform a query that filters by a predicate by creating an event schema and adding the #search directive as seen here

How do I add an OR condition in a graphql where statement?

I cannot get a WHERE statement working with an 'OR' condition in Strapi via graphql playground.
I would like to return all results where either the 'title' OR 'content' fields contain the search_text.
I have tried the following:
articles(where: {
or: [
{"title_contains" : "search_text"},
{"content_contains" : "search_text"}
]
}) {
title
content
}
but an error is returned.
ERROR: "Your filters contain a field 'or' that doesnt appear on your model definition nor it's relations.
Some statements that work (but not what I am after):
where: { "title_contains" : "sometext" }
working, but behaves as an 'AND'
where: {
"title_contains" : "search_text",
"content_contains" : "search_text"
}
As of July it's possible to do it like this
(where: { _or: [{ someField: "1" }, { someField2: "2" }] })
The workaround here is to create a custom Query and make a custom database query that matches your need.
Here is how to create a custom GraphQL query:
https://strapi.io/documentation/3.0.0-beta.x/guides/graphql.html#example-2
To access the data model, you will have to use strapi.models.article (For an Article model) and inside this variable, you will access to native Mongoose or Bookshelf function. So you will be able to query with an OR

How should I query and match data from the same response in GraphQL with Apollo Client and Link Rest?

I have the following query:
const getPage = gql`
query Page($path: String!) {
page(path: $path) #rest(type: "Page", path: "{args.path}") {
blocks #type(name: Block) {
name
posts #type(name: Post) {
body
author
}
}
authors #type(name: Author) {
name
}
}
}
In blocks.posts.author there's only an AuthorId. The authors object is containing all the available authors.
I'd like to replace/match the AuthorId with it's corresponding object. Is it possible to do this within one query?
I also wouldn't mind to have a separate query for Author only (fetch will be cached, no new request would be made), but I still don't know how would I match it through 2 queries.
Example API response
{
blocks: [
{
posts: [
{
id: 1,
title: 'My post',
author: 12,
}
]
}
],
authors: [
{
id: 12,
name: 'John Doe'
}
]
}
What I want with 1 query that author inside a post becomes the full author object.
Great question. With GraphQL, you have the power to expand any field and select the exact subfields you want from it, so if you were using GraphQL on your backend as well this would be a non-issue. There are some workarounds you can do here:
If all of the Author objects are in your Apollo cache and you have access to each Author's id, you could use ApolloClient.readFragment to access other properties, like this:
const authorId = ...; // the id of the author
const authorInfo = client.readFragment({
id: authorId,
fragment: gql`
fragment AuthorInfo on Author {
id
name
# anything else you want here
}
`,
});
Although it's worth noting that with your original query in the question, if you have all of the Author objects as a property of the query, you could just use Javascript operations to go from Author id to object.
const authorId = ...; // the id of the author
data.page.authors.find(author => author.id === authorId);
The following should work.
First, capture the author id as a variable using the #export directive. Then add a new field with some name other than author and decorate it with the #rest, using the exported variable inside the path.
So the query would look something like this:
query Page($path: String!) {
page(path: $path) #rest(type: "Page", path: "{args.path}") {
blocks #type(name: Block) {
name
posts #type(name: Post) {
body
author #export(as: "authorId")
authorFull #rest(
path: '/authors/{exportVariables.authorId}'
type: 'Author'
) {
name
}
}
}
authors #type(name: Author) {
name
}
}
}
You can use the fieldNameNormalizer option to rename the author property in the response to a field with a different name (for example, authorId). Ideally, that should still work with the above so you can avoid having a weird field name like authorFull but apollo-link-rest is a bit wonky so no promises.

GraphQL query based on a specific value of a field

I want to be able to retrieve the latest release from GitHub for a specific repo using their GraphQL API. To do that, I need to get the latest release where isDraft and isPrerelease are false. I have managed to get the first part, but cant figure out how to do the "where" part of the query.
Here is the basic query I have gotten (https://developer.github.com/v4/explorer/):
{
repository(owner: "paolosalvatori", name: "ServiceBusExplorer") {
releases(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) {
nodes {
name
tagName
resourcePath
isDraft
isPrerelease
}
}
}
}
Which returns:
{
"data": {
"repository": {
"releases": {
"nodes": [
{
"name": "3.0.4",
"tagName": "3.0.4",
"resourcePath": "/paolosalvatori/ServiceBusExplorer/releases/tag/3.0.4",
"isDraft": false,
"isPrerelease": false
}
]
}
}
}
}
I cant seem to find a way to do this. Part of the reason is that I am new to GraphQL (first time trying to do a query) and I am not sure how to frame my question.
Can one only "query" based on those types that support arguments (like repository and releases below)? Seems like there should be a way to specify a filter on the field values.
Repository: https://developer.github.com/v4/object/repository/
Releases: https://developer.github.com/v4/object/releaseconnection/
Node: https://developer.github.com/v4/object/release/
Can one only "query" based on those types that support arguments
Yes: GraphQL doesn't define a generic query language in the same way, say, SQL does. You can't sort or filter a field result in ways that aren't provided by the server and the application schema.
I want to be able to retrieve the latest [non-draft, non-prerelease] release from GitHub for a specific repo using their GraphQl API.
As you've already found, the releases field on the Repository type doesn't have an option to sort or filter on these fields. Instead, you can iterate through the releases one at a time with multiple GraphQL calls. These would individually look like
query NextRelease($owner: String!, $name: String!, $after: String) {
repository(owner: $owner, name: $name) {
releases(first: 1,
orderBy: {field: CREATED_AT, direction: DESC},
after: $after) {
pageInfo { lastCursor }
nodes { ... ReleaseData } # from the question
}
}
}
Run this in the same way you're running it now (I've split out the information identifying the repository into separate GraphQL variables). You can leave off the after variable for the first call. If (as in your example) it returns "isDraft": false, "isPrerelease": false, you're set. If not, you need to try again: take the value from the lastCursor in the response, and run the same query, passing that cursor value as the after variable value.
{
repository(owner: "paolosalvatori", name: "ServiceBusExplorer") {
releases(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) {
nodes(isDraft :false , isPrerelease :false ) {
name
tagName
resourcePath
isDraft
isPrerelease
}
}
}
}
Alternatively please have look at GraphQL directives, as sometimes it's required to skip or include the fields on the basis of the values
#skip or #include.
The skip directive, when used on fields or fragments, allows us to exclude fields based on some condition.
The include directive, allows us to include fields based on some condition
GraphQL Directives

Resources