Filter by Array Length in OData - odata

I want to filter based on the length of data. For example, I want to retrieve only the images array that contains more than 1 element. Something like that images.length > 6.
Is this possible using OData. Your help is much appreciated
{
"#search.score": 1,
"site": "core",
"images": [
{
"alt": "Quad"
}
]
}, ,{
"#search.score": 1,
"site": "ccc",
"images": [
{
"alt": "some"
}
]
},
{
"#search.score": 1,
"site": "ccc",
"images": [
{
"alt": "DePaul's Student Center"
},
{
"alt": "O'Malley"
},
{
"alt": "Campus"
},
{
"alt": " Campus"
},
{
"alt": "ith students"
},
{
"alt": "er"
},
{
"alt": "df"
},
{
"alt": "sdf"
},
{
"alt": "df"
},
]

A simpler and better solution is to populate your data with an additional property called ImageCount. If you submit your items via the Azure Search SDK, you can process them before submitting them to search. Add the image count to each item and then filter like normal.
$filter=ImageCount gt 6

Related

Twilio IVR Speech Recognition

I'm new to developing IVR with twilio studio so I started with the basic template and even that's not working.
This is the log:
LOG
Split Based On...
DETAIL
Input evaluated to 'Sales.' from '{{widgets.gather_input.SpeechResult}}'
Transitioning to 'say_play_1' because 'Sales.' did not match any expression
The split is set to "Equal to" sales which then connects the call to a number. It's obviously recognizing the correct speech input but still not working. Any ideas?
{
"description": "IVR",
"states": [
{
"name": "Trigger",
"type": "trigger",
"transitions": [
{
"event": "incomingMessage"
},
{
"next": "gather_input",
"event": "incomingCall"
},
{
"event": "incomingRequest"
}
],
"properties": {
"offset": {
"x": 250,
"y": 50
}
}
},
{
"name": "gather_input",
"type": "gather-input-on-call",
"transitions": [
{
"next": "split_key_press",
"event": "keypress"
},
{
"next": "split_speech_result",
"event": "speech"
},
{
"event": "timeout"
}
],
"properties": {
"voice": "alice",
"speech_timeout": "auto",
"offset": {
"x": 290,
"y": 250
},
"loop": 1,
"hints": "support,sales",
"finish_on_key": "",
"say": "Hello, how can we direct your call? Press 1 for sales, or say sales. To reach support, press 2 or say support.",
"language": "en",
"stop_gather": false,
"gather_language": "en-US",
"profanity_filter": "false",
"timeout": 5
}
},
{
"name": "split_key_press",
"type": "split-based-on",
"transitions": [
{
"event": "noMatch"
},
{
"next": "connect_call_to_sales",
"event": "match",
"conditions": [
{
"friendly_name": "1",
"arguments": [
"{{widgets.gather_input.Digits}}"
],
"type": "equal_to",
"value": "1"
}
]
},
{
"next": "connect_call_to_support",
"event": "match",
"conditions": [
{
"friendly_name": "2",
"arguments": [
"{{widgets.gather_input.Digits}}"
],
"type": "equal_to",
"value": "2"
}
]
}
],
"properties": {
"input": "{{widgets.gather_input.Digits}}",
"offset": {
"x": 100,
"y": 510
}
}
},
{
"name": "split_speech_result",
"type": "split-based-on",
"transitions": [
{
"next": "say_play_1",
"event": "noMatch"
},
{
"next": "connect_call_to_sales",
"event": "match",
"conditions": [
{
"friendly_name": "sales",
"arguments": [
"{{widgets.gather_input.SpeechResult}}"
],
"type": "equal_to",
"value": "sales"
}
]
},
{
"next": "connect_call_to_support",
"event": "match",
"conditions": [
{
"friendly_name": "support",
"arguments": [
"{{widgets.gather_input.SpeechResult}}"
],
"type": "equal_to",
"value": "support"
}
]
}
],
"properties": {
"input": "{{widgets.gather_input.SpeechResult}}",
"offset": {
"x": 510,
"y": 510
}
}
},
{
"name": "connect_call_to_sales",
"type": "connect-call-to",
"transitions": [
{
"event": "callCompleted"
}
],
"properties": {
"offset": {
"x": 100,
"y": 750
},
"caller_id": "{{contact.channel.address}}",
"noun": "number",
"to": "12222222",
"timeout": 30
}
},
{
"name": "connect_call_to_support",
"type": "connect-call-to",
"transitions": [
{
"event": "callCompleted"
}
],
"properties": {
"offset": {
"x": 520,
"y": 750
},
"caller_id": "{{contact.channel.address}}",
"noun": "number",
"to": "12222222",
"timeout": 30
}
},
{
"name": "say_play_1",
"type": "say-play",
"transitions": [
{
"next": "gather_input",
"event": "audioComplete"
}
],
"properties": {
"offset": {
"x": 710,
"y": 200
},
"loop": 1,
"say": "not valid choice."
}
}
],
"initial_state": "Trigger",
"flags": {
"allow_concurrent_calls": true
}
}
Twilio developer evangelist here.
That is weird behaviour, mainly because the log says "evaluated to 'Sales.'". Split widgets conditions are not case-sensitive and should trim leading and following white-space. For some reason this appears to have a capital "S" and a full-stop.
I would suggest a couple of things. Firstly, raise a ticket with Twilio support to look into why the condition didn't match correctly.
Then, try some of the other conditions. When I generate a new IVR template in Studio, the conditions use "Matches any of" instead of "Equal to". You might also try "Contains".
So, experiment with the ways you can match the operators, but get support involved to drill down into why it didn't work in the first place.
The period is needed after the word for some reason...I just put both, for example:
"1, 1., Uno, Uno., Una, Una., Uno, Uno., Español, Español., Español, Español., Español, Español."

Elastic search - aggregation filter for product options

I have a products catalogue where every product is indexed as follows (queried from http://localhost:9200/products/_doc/1) as sample:
{
"_index": "products_20201202145032789",
"_type": "_doc",
"_id": "1",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"title": "Roncato Eglo",
"description": "Amazing LED light made of wood and description continues.",
"price": 3990,
"manufacturer": "Eglo",
"category": [
"Lights",
"Indoor lights"
],
"options": [
{
"title": "Mount type",
"value": "E27"
},
{
"title": "Number of bulps",
"value": "4"
},
{
"title": "Batteries included",
"value": "true"
},
{
"title": "Ligt temperature",
"value": "warm"
},
{
"title": "Material",
"value": "wood"
},
{
"title": "Voltage",
"value": "230"
}
]
}
}
Every option contains different value, so there are many Mount type values, Light temperature values, Material values, and so on.
How can I create an aggregation (filter) where I can let customers choose between various Mount Type options:
[ ] E27
[X] E14
[X] GU10
...
Or let them choose from different Material options displayed as checkboxes:
[X] Wood
[ ] Metal
[ ] Glass
...
I can handle it on frontend once the buckets are created. Creation of different buckets for these options is What I am struggling with.
I have succesfully created and displayed and using aggregations for Category, Manufacturer and other basic ones. Thes product options are stored in has_many_through relationships in database. I am using Rails + searchkick gem, but those allow me to create raw queries to elastic search.
The prerequisite for such aggregation is to have options field as nested.
Sample index mapping:
PUT test
{
"mappings": {
"properties": {
"title": {
"type": "keyword"
},
"options": {
"type": "nested",
"properties": {
"title": {
"type": "keyword"
},
"value": {
"type": "keyword"
}
}
}
}
}
}
Sample docs:
PUT test/_doc/1
{
"title": "Roncato Eglo",
"options": [
{
"title": "Mount type",
"value": "E27"
},
{
"title": "Material",
"value": "wood"
}
]
}
PUT test/_doc/2
{
"title": "Eglo",
"options": [
{
"title": "Mount type",
"value": "E27"
},
{
"title": "Material",
"value": "metal"
}
]
}
Assumption: For a given document a title under option appears only once. For e.g. there can exists only one nested document under option having title as Material.
Query for aggregation:
GET test/_search
{
"size": 0,
"aggs": {
"OPTION": {
"nested": {
"path": "options"
},
"aggs": {
"TITLE": {
"terms": {
"field": "options.title",
"size": 10
},
"aggs": {
"VALUES": {
"terms": {
"field": "options.value",
"size": 10
}
}
}
}
}
}
}
}
Response:
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"OPTION" : {
"doc_count" : 4,
"TITLE" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "Material",
"doc_count" : 2,
"VALUES" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "metal",
"doc_count" : 1
},
{
"key" : "wood",
"doc_count" : 1
}
]
}
},
{
"key" : "Mount type",
"doc_count" : 2,
"VALUES" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "E27",
"doc_count" : 2
}
]
}
}
]
}
}
}
}

Is there a way to enter dollars/cents via DTMF?

I'm currently working on a pay-by-phone system with this flow:
<Gather> customer's account number with us
<Gather> amount to pay
<Pay> - with the previous 2 <Gather>s passed as variables to Stripe
The issue I am having is collecting the amount. Is there a way to convert the amount from a single string to a decimal string?
eg. 12300 becomes 123.00
All suggestions welcome as the current working theory is to add a third <Gather> just for the cents but this feels cumbersome from a UX/UI perspective.
I edited this answer, since I was successful in accomplishing this.
I did it with STUDIO and FUNCTIONS.
Prompt the caller to enter the amount using star key () as a decimal. For example to enter $5.43 they should enter 543 and then press #.
then I took the gathered digits and sent them out as a parameter in the FUNCTION widget by inserting "amount" as the KEY and {{widgets.gather_1.digits}} as the VALUE of the parameter.
I wrote the following function:
exports.handler = function(context, event, callback) {
const rawamount = event.amount;
const decimalamount = rawamount.replace("*",".");
const response = decimalamount;
callback(null, response);
};
then in the <pay> widget I inserted {{widget.function_1.body}} in the AMOUNT field.
Your payment should process with dollars and cents.
Let me know if this worked. If you need screenshots of my studio flow let me know.
ADDED:
Here is a sample flow if someone wants to use as a template (someone requested it)
Open a blank Studio flow.
In the trigger widget under "config" tab click on "SHOW FLOW JSON" delete all previous code and paste code below inside and save.
Your flow should generate.
Then create function. Code and instructions below after JSON.
PLEASE NOTE: though a function may pop up in the flow, you won't be able to access it, and will have to create it under your account.
{
"description": "A New Flow",
"states": [
{
"name": "Trigger",
"type": "trigger",
"transitions": [
{
"event": "incomingMessage"
},
{
"next": "gather_1",
"event": "incomingCall"
},
{
"event": "incomingRequest"
}
],
"properties": {
"offset": {
"x": 0,
"y": 0
}
}
},
{
"name": "gather_1",
"type": "gather-input-on-call",
"transitions": [
{
"next": "function_1",
"event": "keypress"
},
{
"event": "speech"
},
{
"event": "timeout"
}
],
"properties": {
"voice": "man",
"speech_timeout": "auto",
"offset": {
"x": 30,
"y": 270
},
"loop": 1,
"finish_on_key": "#",
"say": "Please enter an amount, then press pound. To enter with cents use the star key. for example to enter $6.25 press six star two five.",
"language": "en-US",
"stop_gather": true,
"gather_language": "en",
"profanity_filter": "true",
"timeout": 5
}
},
{
"name": "function_1",
"type": "run-function",
"transitions": [
{
"next": "say_play_2",
"event": "success"
},
{
"event": "fail"
}
],
"properties": {
"offset": {
"x": -60,
"y": 530
},
"parameters": [
{
"value": "{{widgets.gather_1.Digits}}",
"key": "amount"
}
],
"url": "https://charcoal-sloth-2579.twil.io/insert-decimal"
}
},
{
"name": "say_play_2",
"type": "say-play",
"transitions": [
{
"next": "pay_1",
"event": "audioComplete"
}
],
"properties": {
"voice": "man",
"offset": {
"x": -59,
"y": 839
},
"loop": 1,
"say": "You entered ${{widgets.function_1.body}}",
"language": "en-US"
}
},
{
"name": "pay_1",
"type": "capture-payments",
"transitions": [
{
"next": "say_play_3",
"event": "success"
},
{
"next": "say_play_4",
"event": "maxFailedAttempts"
},
{
"next": "say_play_4",
"event": "providerError"
},
{
"next": "say_play_4",
"event": "payInterrupted"
},
{
"next": "say_play_4",
"event": "hangup"
},
{
"next": "say_play_4",
"event": "validationError"
}
],
"properties": {
"security_code": true,
"offset": {
"x": -70,
"y": 1140
},
"max_attempts": 2,
"payment_connector": "Stripe_Connector",
"payment_amount": "{{widgets.function_1.body}}",
"currency": "usd",
"language": "en-US",
"postal_code": "false",
"payment_token_type": "one-time",
"timeout": 5,
"valid_card_types": [
"visa",
"master-card",
"amex",
"discover"
]
}
},
{
"name": "say_play_3",
"type": "say-play",
"transitions": [
{
"event": "audioComplete"
}
],
"properties": {
"voice": "man",
"offset": {
"x": -185,
"y": 1433
},
"loop": 1,
"say": "Your payment ${{widgets.function_1.body}}was successful, Thank you.",
"language": "en-US"
}
},
{
"name": "say_play_4",
"type": "say-play",
"transitions": [
{
"event": "audioComplete"
}
],
"properties": {
"voice": "man",
"offset": {
"x": 190,
"y": 1456
},
"loop": 1,
"say": "There was an error with your payment. Goodbye!",
"language": "en-US"
}
}
],
"initial_state": "Trigger",
"flags": {
"allow_concurrent_calls": true
}
}
Create a function with this code:
exports.handler = function(context, event, callback) {
const amount = event.amount;
const convert = amount.replace('*', '.');
callback(null, convert);
};
Make sure in the flow you check that the function widget has the right function selected and that you insert the following parameter KEY:amount VALUE: {{widgets.YOUR_GATHER_WIDGET NAME.body}}

How to Select multiple work items programmatically in Azure devops?

I am writing a Task Creator extension for ADO where we can create multiple tasks for multiple WITs.
Currently, I have a working extension where it allows to create multiple tasks on a single PBI.
This is how my manifest file looks
{
"manifestVersion": 1,
"id": "taskcreatortest",
"version": "1.0.24",
"name": "TaskCreator",
"description": "Create bulk tasks for a Work Item",
"publisher": "ZankhanaRana",
"galleryFlags": [
"Preview"
],
"icons": {
"default": "static/images/logo.png"
},
"scopes": [
"vso.work_write",
"vso.work",
"vso.code"
],
"targets": [
{
"id": "Microsoft.VisualStudio.Services"
}
],
"screenshots": [
{
"path": "static/images/menu.png"
},
{
"path": "static/images/createtaskform.png"
}
],
"demands": [
"api-version/3.0"
],
"tags": [
"TFS/VSTS Task Creator","Task"
],
"content": {
"details": {
"path": "overview.md"
},
"license": {
"path": "license.md"
}
},
"links": {
"getstarted": {
"uri": "https://bit.ly"
},
"support": {
"uri": "https://bit.ly"
},
"issues": {
"uri": "https://bit.ly"
}
},
"repository": {
"type": "git",
"uri": "https://bit.ly"
},
"branding": {
"color": "rgb(220, 235, 252)",
"theme": "light"
},
"files": [ ... ],
"categories": [
"Azure Test Plans"
],
"contributions": [
{
"id": "createtasks-context-menu",
"type": "ms.vss-web.action",
"description": "Toolbar item to create tasks for a work item",
"targets":[
"ms.vss-work-web.work-item-context-menu",
"ms.vss-work-web.work-item-toolbar-menu"
],
"properties": {
"uri": "static/index.html",
"text": "Create Tasks",
"title": "Create multiple tasks for a work item",
"toolbarText": "Create Tasks",
"groupId":"core"
}
},
{
"id": "createTasks-Form",
"type": "ms.vss-web.control",
"description": "Select task to create",
"targets": [ ],
"properties": {
"uri": "static/createTaskForm.html"
}
}
]
}
I am unable to select multiple work items, right-click and Create tasks.
An alert message 'Select only one item' pops up.
There are other custom extensions I installed, for example, Work Item Visualization by Microsoft Devlabs which allows multiple item selection. I think it has something to do with my configuration/manifest file.
Can someone point at what am I doing wrong?
I found the solution to this problem. Initially, I thought it has something to do with the extension configuration file (vss-extension.json); but I had a code in my script which checked for the number of items selected and if the number of items is greater than 1, it returned the alert box.
I changed that condition and everything run fine.
VSS.ready(function () {
VSS.register(VSS.getContribution().id, function (context) {
return {
execute: function (actionContext) {
if (actionContext.workItemDirty)
showDialog("Please save your work item first");
else if (actionContext.workItemIds && actionContext.workItemIds.length > 1)
showDialog("Select only one work item");
else {
var workItemType = getWorkItemType(actionContext)
if ($.inArray(workItemType, allowedWorkItemTypes) >= 0)
showPropertiesInDialog(actionContext, "Create Tasks");
else
showDialog("Not available for " + workItemType);
}
}
};
});
VSS.notifyLoadSucceeded();
});

How to make Elasticsearch sort/prefer hits with exactly matching strings first

I'm using default analyzers and indexing. So let's say I have this simple mapping:
"question": {
"properties": {
"title": {
"type": "string"
},
"answer": {
"properties": {
"text": {
"type": "string"
}
}
}
}
}
(that was an example. sorry if it has typos)
Now, I perform the following search.
GET _search
{
"query": {
"query_string": {
"query": "yes correct",
"fields": ["answer.text"]
}
}
}
The results will score a text value like "yes correct." (doc id value 1) higher than simply "yes correct" (without a period, doc id value 181). Both hits have the same score value, but the hits array lists the one with the smaller doc id first. I understand that the default index option includes sorting by doc id, so how do I exclude that one attribute and still use the rest of the default options?
I'm not setting any custom analyzers, so everything is using default values for Elasticsearch 2.0.
This is probably a use case for Dis Max Query
A query that generates the union of documents produced by its
subqueries, and that scores each document with the maximum score for
that document as produced by any subquery, plus a tie breaking
increment for any additional matching subqueries.
So following that, you need to make your answer score as an exact match and give it highest boost. You'll have to use a custom analyzer for that. That'd be your mappings:
PUT /test
{
"settings": {
"analysis": {
"analyzer": {
"my_keyword": {
"type": "custom",
"tokenizer": "keyword",
"filter": [
"asciifolding",
"lowercase"
]
}
}
}
},
"mappings": {
"question": {
"properties": {
"title": {
"type": "string"
},
"answer": {
"type": "object",
"properties": {
"text": {
"type": "string",
"analyzer": "my_keyword",
"fields": {
"stemmed": {
"type": "string",
"analyzer": "standard"
}
}
}
}
}
}
}
}
}
Your test data:
PUT /test/question/1
{
"title": "title nr1",
"answer": [
{
"text": "yes correct."
}
]
}
PUT /test/question/2
{
"title": "title nr2",
"answer": [
{
"text": "yes correct"
}
]
}
Now when you're querying for "yes correct." using such query:
POST /test/_search
{
"query": {
"dis_max": {
"tie_breaker": 0.7,
"boost": 1.2,
"queries": [
{
"match": {
"answer.text": {
"query": "yes correct.",
"type": "phrase"
}
}
},
{
"match": {
"answer.text.stemmed": {
"query": "yes correct.",
"operator": "and"
}
}
}
]
}
}
}
You get this output:
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 0.37919715,
"hits": [
{
"_index": "test",
"_type": "question",
"_id": "1",
"_score": 0.37919715,
"_source": {
"title": "title nr1",
"answer": [
{
"text": "yes correct."
}
]
}
},
{
"_index": "test",
"_type": "question",
"_id": "2",
"_score": 0.11261705,
"_source": {
"title": "title nr2",
"answer": [
{
"text": "yes correct"
}
]
}
}
]
}
}
If you run very same query without trailing dot, which then becomes "yes correct", you're getting this result:
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 0.37919715,
"hits": [
{
"_index": "test",
"_type": "question",
"_id": "2",
"_score": 0.37919715,
"_source": {
"title": "title nr2",
"answer": [
{
"text": "yes correct"
}
]
}
},
{
"_index": "test",
"_type": "question",
"_id": "1",
"_score": 0.11261705,
"_source": {
"title": "title nr1",
"answer": [
{
"text": "yes correct."
}
]
}
}
]
}
}
Hopefully this is what you're looking for.
By the way, I'd recommend to always use Match query when performing text search. Taken from documentation:
Comparison to query_string / field The match family of queries
does not go through a "query parsing" process. It does not support
field name prefixes, wildcard characters, or other "advanced"
features. For this reason, chances of it failing are very small / non
existent, and it provides an excellent behavior when it comes to just
analyze and run that text as a query behavior (which is usually what a
text search box does). Also, the phrase_prefix type can provide a
great "as you type" behavior to automatically load search results.
Elasticsearch or rather Lucene scoring does not take into account the relative positioning of the tokens. It utlizes 3 different criterias to do the same
Term frequency - Frequency at which the search terms is present in
the document
Inverse document frequency - Number of occurrence of the search term
in the entire database. The more the occurance , the more the common
is the search term and less the importance it has in search
Field length normalization - Number of tokens present in the target
field.
You can learn more about it here.

Resources