Elastic Search Nest Fuzzy per Field - elasticsearch

I am struggling with Elasticsearch using NEST for C#.
Let's assume an index of UserAccounts which looks like
[{
AccountId: 1,
Name: "Test Account",
Email: "test#test.com",
Phone: "01234/5678",
Street: "test Street 1",
Zip: "12345"
},
{
AccountId: 2,
Name: "Test Akkount",
Email: "test#gmail.com",
Phone: "0987/6543"
Street: "test Street 1",
Zip: "54321"
},
{
AccountId: 3,
Name: "Bla Bla",
Email: "qwer#yahoo.com",
Phone: null,
Street: "bla Street 3",
Zip: "45678
},
{
AccountId: 4,
Name: "Foo",
Email: "asdf#msn.com",
Phone: null,
Street: "ghjk Street 9",
Zip: "65487,
}]
now I want get all accounts similar to my query.
string name = "aggount";
string email = "test#gmail.com";
string phone = "0987/6543";
string street = "test Str 1"
string zip = "54321"
But each field has its own criteria.
Field "name" should match over fuzzy logic.
Field "email" should match to 100% but not when its null or empty.
Field "phone" should match to 100% but not when its null or empty.
Field "street" should only match with fuzzy, when "zip" matches to 100%.
I want a list of account with possibilities. If name matches but email not, than there should be a result because of name. Do elastic trim always the provided values?
If it is possible to get a score per field. But this is a nice to have.
My code do not work because when I provide a email and the email is not matching, elastic skip the match over name.
var response = elasticClient.Search<Accounts>(search => search
.Index(INDEX_NAME_ACCOUNT)
.Query(q => q.Bool(b =>
{
if (name != null)
{
b = b.Should(s => s.Match(m => m.Query(name).Field(f => f.Name).Boost(1.5).Fuzziness(Fuzziness.EditDistance(3))));
}
if (street != null && zipCode != null)
{
b = b.Should(s =>
s.Match(m => m.Query(street).Field(f => f.MainAddress.Street).Boost(0.5).Fuzziness(Fuzziness.EditDistance(3))) &&
s.Match(m => m.Query(zipCode).Field(f => f.MainAddress.Zip).Boost(0.7).Fuzziness(Fuzziness.EditDistance(0)))
);
}
if (string.IsNullOrEmpty(name1) && string.IsNullOrEmpty(street))
{
b = b.Should(s => s.MatchNone());
}
b = b.MustNot(s => s.Match(m => m.Query(null).Field(f => f.DeletedTimestamp)));
return b;
}))
.Explain()
.Human()
);
Thank you in advance

Related

Using graphql playground to test Saleor productCreate, what's the correct syntax for adding a product description?

In the code example below, if I exclude the description field the product is created successfully. With the description field in place I get a GraphQL error.
The code:
productCreate(
input: {
category: "Q2F0ZWdvcnk6MQ==", # Category ID
name: "Delete Me!", # Product name
productType: "UHJvZHVjdFR5cGU6MQ==", # Product Type ID
chargeTaxes: true,
weight: "0.3", # in Kg
rating: 5,
description: {text:"some text"}, # nope
}
)
The error:
graphql.error.base.GraphQLError: Argument \"input\" has invalid value {category: \"Q2F0ZWdvcnk6MQ==\", name: \"Delete Me!\", productType: \"UHJvZHVjdFR5cGU6MQ==\", chargeTaxes: true, weight: \"0.3\", rating: 5, description: {text: \"some text\"}}.",
"In field \"description\": Expected type \"JSONString\", found {text: \"some text\"}."
It is a string, for rich text it is using https://editorjs.io/
You can inspect the network tab in the dashboard to learn how APIs are being used
JSON string means providing a JSON text converted to a string. This can be achieved by escaping quotation marks within the JSON.
For example, this JSON
{ "text": "some text" }
can be converted to String as below:
"{\"text\":\"sometext\"}"
As you notice that the text encapsulated inside quotation marks, to be a valid String.
You can use https://jsontostring.com/ for the conversion
Your final code should be like this:
mutation {
productCreate(
input: {
category: "Q2F0ZWdvcnk6MQ==" # Category ID
name: "Delete Me!" # Product name
productType: "UHJvZHVjdFR5cGU6MQ==" # Product Type ID
chargeTaxes: true
weight: "0.3" # in Kg
rating: 5
description: "{\"text\":\"sometext\"}" # nope
}
){
product{
id
}
}
}
Sorting out the description syntax wasn't straightforward. From my question here:
Saleor on Github
I got this answer:
{
"id": "UHJvZHVjdDo3Mg==",
"description": "{\"blocks\":[{\"type\":\"paragraph\",\"data\":{\"text\":\"New description\"}}]}"
}
which I then implemented like this:
query = gql(
"""
mutation (
$slug: String!,
$product_title: String!,
$description: JSONString!,
$weight_grams: WeightScalar!,
)
{
productCreate(
input:{
category: "Q2F0ZWdvcnk6NQ==",
name: $product_title,
productType: "UHJvZHVjdFR5cGU6MQ==",
slug: $slug,
description: $description,
weight: $weight_grams,
}
)
{
errors {
field
message
}
product {
id
name
productType {
id
}
slug
}
}
}
"""
)
params = {
"product_title": str(product_title),
"description": '{"blocks":[{"type":"paragraph","data":{"text":"'
+ str(product_title + " (" + csv_product_id + ")")
+ '"}}]}',
"slug": str(csv_sku_code),
"weight_grams": str(weight_grams),
}
result = client.execute(query, variable_values=params)
This works well for us.

GraphQL - Relationship returning null

I started to learn GraphQL and I'm trying to create the following relationship:
type User {
id: ID!,
name: String!,
favoriteFoods: [Food]
}
type Food {
id: ID!
name: String!
recipe: String
}
So basically, a user can have many favorite foods, and a food can be the favorite of many users. I'm using graphql.js, here's my code:
const Person = new GraphQLObjectType({
name: 'Person',
description: 'Represents a Person type',
fields: () => ({
id: {type: GraphQLNonNull(GraphQLID)},
name: {type: GraphQLNonNull(GraphQLString)},
favoriteFoods: {type: GraphQLList(Food)},
})
})
const Food = new GraphQLObjectType({
name: 'Food',
description: 'Favorite food(s) of a person',
fields: () => ({
id: {type: GraphQLNonNull(GraphQLID)},
name: {type: GraphQLNonNull(GraphQLString)},
recipe: {type: GraphQLString}
})
})
And here's the food data:
let foodData = [
{id: 1, name: 'Lasagna', recipe: 'Do this then that then put it in the oven'},
{id: 2, name: 'Pancakes', recipe: 'If you stop to think about, it\'s just a thin, tasteless cake.'},
{id: 3, name: 'Cereal', recipe: 'The universal "I\'m not in the mood to cook." recipe.'},
{id: 4, name: 'Hashbrowns', recipe: 'Just a potato and an oil and you\'re all set.'}
]
Since I'm just trying things out yet, my resolver basically just returns a user that is created inside the resolver itself. My thought process was: put the food IDs in a GraphQLList, then get the data from foodData usind lodash function find(), and replace the values in person.favoriteFoods with the data found.
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
description: 'Root Query',
fields: {
person: {
type: Person,
resolve(parent) {
let person = {
name: 'Daniel',
favoriteFoods: [1, 2, 3]
}
foodIds = person.favoriteFoods
for (var i = 0; i < foodIds.length; i++) {
person.favoriteFoods.push(_.find(foodData, {id: foodIds[i]}))
person.favoriteFoods.shift()
}
return person
}
}
}
})
But the last food is returning null. Here's the result of a query:
query {
person {
name
favoriteFoods {
name
recipe
}
}
}
# Returns
{
"data": {
"person": {
"name": "Daniel",
"favoriteFoods": [
{
"name": "Lasagna",
"recipe": "Do this then that then put it in the oven"
},
{
"name": "Pancakes",
"recipe": "If you stop to think about, it's just a thin, tasteless cake."
},
null
]
}
}
}
Is it even possible to return the data from the Food type by using only its ID? Or should I make another query just for that? In my head the relationship makes sense, I don't think I need to store the IDs of all the users that like a certain food in the foodData since it has an ID that I can use to fetch the data, so I can't see the problem with the code or its structure.
Calling shift and push on an array while iterating through that same array will invariably lead to some unexpected results. You could make a copy of the array, but it'd be much easier to just use map:
const person = {
name: 'Daniel',
favoriteFoods: [1, 2, 3],
}
person.favoriteFoods = person.favoriteFoods.map(id => {
return foodData.find(food => food.id === id)
})
return person
The other issue here is that if your schema returns a Person in another resolver, you'll have to duplicate this logic in that resolver too. What you really should do is just return the person with favoriteFoods: [1, 2, 3]. Then write a separate resolver for the favoriteFoods field:
resolve(person) {
return person.favoriteFoods.map(id => {
return foodData.find(food => food.id === id)
})
}

Dynamo DB Query using begins_with in ruby

Below is the my dynamodb data
Table Name : Inventory
PartitionKey: Name (string)
SortKey : ID (String)
Below is sample data.
Name ID
Fruits Mango
Fruits Mango-Green
Fruits Mango-Green-10
Fruits Mango-Green-20
Fruits Apple
Fruits Apple-Red
Veggie Onion
Veggie Onion-White
Veggie Onion-White-10
How can I add the search to the below code to return all the rows that begins_with "Mango-Green" ? I cant modify the keys or the table data now.
table_name: 'Inventory',
key_condition_expression: "#Name = :Name AND #ID = :ID",
select: "ALL_ATTRIBUTES",
expression_attribute_names: {
"#ID" => "ID",
"#product" => "product"
},
expression_attribute_values: {
":ID" => ID,
":Name" => 'Fruits'
}
more info about ruby interface into code as comments
require 'dotenv/load'
require 'aws-sdk-dynamodb'
# https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/DynamoDB.html
client_dynamodb = Aws::DynamoDB::Client.new(
region: ENV['AWS_REGION'],
credentials: Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY'])
)
# https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/DynamoDB/Client.html#query-instance_method
fruits = client_dynamodb.query({
table_name: "Inventory",
projection_expression: "name, id",
key_conditions: {
"id" => {
attribute_value_list: ["Mango-Green"],
comparison_operator: "BEGINS_WITH",
}
},
scan_index_forward: false, # true => asc ; false => desc
}).items

How to write a mutation resolver to change one or more specific values of an object?

It seems reasonable to expect one resolver to handle input for any combination of one or more of an object's values. I shouldn't have to write separate resolvers for 'title', 'published', 'author', etc., right?
Here's my example object:
let books = [
{
title: 'Clean Code',
published: 2008,
author: 'Robert Martin',
id: 'afa5b6f4-344d-11e9-a414-719c6709cf8e',
genres: ['refactoring'],
},
{
title: 'Agile software development',
published: 2002,
author: 'Robert Martin',
id: 'afa5b6f5-344d-11e9-a414-719c6709cf9e',
genres: ['agile', 'patterns', 'design'],
},
]
typeDefs:
const typeDefs = gql`
type Book {
title: String
published: Int
author: String
id: ID
genres: [String]
}
type Query {
bookCount: Int!
allBooks(title: String, author: String, genre: String): [Book]
findBooks(title: String!): Book
}
type Mutation {
addBook(
title: String!
published: Int
author: String!
genres: [String]
): Book
editBook(
id: ID
title: String
published: Int
author: String
genres: [String]
): Book
}
`
Here's the resolver I currently have:
Mutation: {
editBook: (_, args) => {
const book = books.find(b => b.id === args.id)
if (!book) {
return null
}
const updatedBook = {
...book,
title: args.title,
author: args.author,
published: args.published,
genres: [args.genres],
}
books = books.map(b => (
b.id === args.id ? updatedBook : b))
return updatedBook
},
}
Here's what is currently happening.
Original object:
"allBooks": [
{
"id": "afa5b6f4-344d-11e9-a414-719c6709cf8e",
"title": "Clean Code",
"author": "Robert Martin",
"published": 2008,
"genres": [
"refactoring"
]
},
{...}
]
Mutation query:
mutation {
editBook(id:"afa5b6f4-344d-11e9-a414-719c6709cf8e", title:"changed"){
id
title
author
published
genres
}
}
Returns this:
{
"data": {
"editBook": {
"id": "afa5b6f4-344d-11e9-a414-719c6709cf8e",
"title": "changed",
"author": null,
"published": null,
"genres": [
null
]
}
}
}
How do I write the resolver to change one or more of an object's values, without changing the unspecified values to 'null'?
My javascript skills are, I'll admit, rather shaky, and I'm guessing the answer lies with a more eloquent map function, but since the code runs inside a graphql schema module, it doesn't handle console.log so troubleshooting is problematic. Any recommendations to address that would be extremely helpful as well, so I can troubleshoot my own problems better.
Couple of points
There's no need to use map or to reassign books. Because the book variable is a reference to the object in your books array, you can mutate it (but not reassign it) and you will mutate the object in the array. See this question for additional details.
const book = books.find(b => b.id === args.id)
if (book) {
// you cannot reassign book and still have the original value change,
// but you can mutate it
book.title = args.title
book.author = args.author
book.published = args.published
book.genres = args.genres
}
return book
To change only the values present in args, you have some options:
You can use the logical OR operator (||) to provide another value to assign if the first value is falsey. Here, book.title will only be used if args.title is undefined, null, 0, NaN, "", or false.
book.title = args.title || book.title
You can use Object.assign, which copies the properties from one object to another. Since args will be missing those properties that aren't provided, you can just do something like the below snippet. This is much more elegant and concise, but will only work if your argument names match your property names.
Object.assign(book, args)
You can loop through the args object. This might be more desirable if you have one or more arguments that don't match the properties of book:
Object.keys(args).forEach(argName => {
const argValue = args[argName]
book[argName] = argValue
})
Putting it all together, we might do:
const book = books.find(b => b.id === args.id)
if (book) {
Object.assign(book, args)
}
return book

Merge 2 arrays of objects subdocs based on id field

I have a M-N relation of users and teams & I'm trying to use a subdoc strategy instead of a standard 3-table SQL strategy.
user = {
id: 'user1',
teams: [{'team1', roles: ['admin']}, {'team2', roles: ['editor']}]
}
team = {
id: 'team1',
name: 'The Team #1'
}
I'd like to grab the name field from the team table and stick it in the appropriate subdoc:
query = {
id: 'user1',
teams: [{'team1', roles: ['admin'], name: 'The Team #1'}, {'team2', roles: ['editor'], name: 'The Team #2'}]
}
I can get the team doc easily enough, but I keep overwriting the teams array:
//Query
r.table('users').get('user1').merge(function(user) {
return {
teams: r.table('teams').getAll(r.args(user('teams')
.map(function(team) {return team('id')}))).coerceTo('array')
}
})
//Bad result
user.teams = [
{
"id": "team1" ,
"name": "team 1"
} ,
{
"id": "team2" ,
"name": "team 2"
}
]
Is it possible to merge an array of objects based on an object field, or should I do this at the application level? Is there a better way?
If the teams array has the ID of the team, you can do something like this:
r.table('users').get('user1').merge(function(user) {
return {teams: user('teams').merge(function(team) {
return r.table('teams').get(team('id'));
})};
})

Resources