How to execute a bulk ElasticSearch operation from an AppSync resolver? - elasticsearch

I'm trying to execute a bulk operation over an ElasticSearch index from a VTL AppSync resolver.
Specifically this mutation:
input CreateTopicsInput {
language: String!
texts: [String!]!
}
type Mutation {
createTopics(input: CreateTopicsInput!): [Topic!]
}
And I created the following resolver:
Mutation.createTopics.req.vtl
#set( $body = "" )
#set( $q = '"' )
#foreach( $text in $ctx.args.input.texts )
#set( $body = $body.concat("{ ${q}index${q}: { ${q}_id${q}: ${q}${text}${q} } }
") )
#set( $body = $body.concat("{ ${q}text${q}: ${q}$text${q}, ${q}suggest${q}: { ${q}input${q}: ${q}$text${q} } }
") )
#end
{
"version": "2017-02-28",
"operation": "POST",
"path": "/topic-${ctx.args.input.language}/doc/_bulk",
"params": {
"headers" : [ { "Content-Type" : "application/x-ndjson" } ],
"body": "$body"
}
}
And when executed for example with this data:
mutation CreateTopic {
createTopics(input: {
language: "en",
texts: [ "COVID", "Election" ]}) {
text
}
}
Seems to output a correct invocation:
{
"version": "2017-02-28",
"operation": "POST",
"path": "/topic-en/doc/_bulk",
"params": {
"headers" : [ { "Content-Type" : "application/x-ndjson" } ],
"body": "{ "index": { "_id": "COVID" }
{ "text": "COVID" }
{ "index": { "_id": "Election" }
{ "text": "Election" }
"
}
}
but it doesn't work. Specifically it throws:
{
"data": {
"createTopics": null
},
"errors": [
{
"path": [
"createTopics"
],
"data": null,
"errorType": "MappingTemplate",
"errorInfo": null,
"locations": [
{
"line": 2,
"column": 3,
"sourceName": null
}
],
"message": "Unexpected character ('i' (code 105)): was expecting comma to separate Object entries\n at [Source: (String)\"{\n \"version\": \"2017-02-28\",\n \"operation\": \"POST\",\n \"path\": \"/topic-en/doc/_bulk\",\n \"params\": {\n \"headers\" : { \"Content-Type\" : \"application/x-ndjson\" },\n \"body\": \"{ \"index\": { \"_id\": \"CODID\" } }\n{ \"text\": \"CODID\", \"suggest\": { \"input\": \"CODID\" } }\n{ \"index\": { \"_id\": \"Election\" } }\n{ \"text\": \"Election\", \"suggest\": { \"input\": \"Election\" } }\n\"\n }\n}\n\"; line: 7, column: 18]"
}
]
}
Some idea?

i played a little bit around and figure out that you would need to
quote your double quotes
write all in one line, divided by a "\n"
the last part in that line needs to be an "\n" again
As example:
{
"version": "2018-05-29",
"method": "POST",
"resourcePath": "/_msearch/template",
"params": {
"headers": {
"Content-Type": "application/x-ndjson"
},
"body": "{ \"index\": { \"_id": \"COVID\" }\n{ \"text\": \"COVID\" }\n"
}
}
Of course, this is not very convinced and for your use case this could work better:
#set($ndjson = [])
$util.qr($ndjson.add({ "index": "COVID" }))
$util.qr($ndjson.add({ "text": "COVID", "Param2": "vaccine" } }))
{
"version": "2018-05-29",
"method": "POST",
"resourcePath": "/_msearch/template",
"params": {
"headers": {
"Content-Type": "application/x-ndjson"
},
"body": "#foreach ($line in $ndjson)$util.escapeJavaScript($util.toJson($line))\n#end"
}
}
The (maybe) problematic part could be the JavaScript Escaping. Not sure how your data looks like but it could result into some more quoted data as needed.
P.s: i switched my implementation to the last mentioned example and it works fine for the multi search.

Related

Wiremock request.query not working with a paramter that contains dots

I'm using sprint-boot with wiremock-jre8, what i'm trying to to is to get a value from URL params, but this value contains dots "product.productCharacteristic.value"
{
"request": {
"urlPath": "/exampleOfPath",
"method": "GET",
"queryParameters": {
"product.productCharacteristic.name": {
"equalTo": "MSISDN"
},
"product.productCharacteristic.value": {
"matches": ".*"
}
}
},
"response": {
"status": 200,
"jsonBody": {
"key": "the value should be here {{request.query['product.productCharacteristic.value']}}"
},
"transformers": [
"response-template"
],
"headers": {
"Content-Type": "application/json"
}
}
}
i have tested all of those
{{request.query.['product.productCharacteristic.value']}}
{{request.query['product.productCharacteristic.value']}}
{{request.query['product%2EproductCharacteristic%2Evalue']}}
{{request.query.['product%2EproductCharacteristic%2Evalue']}}
{{request.query.product%2EproductCharacteristic%2Evalue}}
{{request.query.product.productCharacteristic.value}}
{{lookup request.query 'product.productCharacteristic.value'}}
{{lookup request.query 'product%2EproductCharacteristic%2Evalue'}}
You can use this format:
{{request.query.[product.productCharacteristic.value]}}
See WireMock - Response Templating - The request model:
request.headers.[<key>] - Header with awkward characters e.g. request.headers.[$?blah]

How can I change a json structure into an object and rename keys with jq

Using jq I am trying to convert the rawe json below into the desired json outcome.
Objectives:
name renamed to pathParameterName
type renamed to datasetParameter
Raw Json I'm trying to convert
{
"pathOptions": {
"parameters": {
"raw_date": {
"name": "raw_date",
"type": "Datetime",
"datetimeOptions": {
"localeCode": "en-GB"
},
"createColumn": true,
"filter": {
"expression": "(after :date1)",
"valuesMap": {
":date1": "2022-03-08T00:00:00.000Z"
}
}
}
}
}
}
Json desired outcome:
{
"pathOptions": {
"parameters": [
{
"pathParameterName": "raw_date",
"datasetParameter": {
"name": "raw_date",
"type": "Datetime",
"datetimeOptions": {
"localeCode": "en-GB"
},
"createColumn": true,
"filter": {
"expression": "(after :date1)",
"valuesMap": [
{
"valueReference": ":date1",
"value": "2022-03-08T00:00:00.000Z"
}
]
}
}
}
]
}
}
This is what I have so far:
map_values(if type == "object" then to_entries else . end)
This is what my code above currently produces. -I'm struggling with the key renaming.
{
"pathOptions": [
{
"key": "parameters",
"value": [
{
"pathParameterName": "raw_date",
"datasetParameter": {
"name": "raw_date",
"type": "Datetime",
"datetimeOptions": {
"localeCode": "en-GB"
},
"createColumn": true,
"filter": {
"expression": "(after :date1)",
"valuesMap": [
{
"valueReference": ":date1",
"value": "2022-03-08T00:00:00.000Z"
}
]
}
}
}
]
}
]
}
The function to_entries, "converts between an object and an array of key-value pairs" (see the manual). To rename the preset key and value fields, just reassign them to a new name with a new object as in {valueReference: .key, value}.
jq '
.pathOptions.parameters |= (
to_entries | map({
pathParameterName: .key,
datasetParameter: (
.value | .filter.valuesMap |= (
to_entries | map({valueReference: .key, value})
)
)
})
)
'
{
"pathOptions": {
"parameters": [
{
"pathParameterName": "raw_date",
"datasetParameter": {
"name": "raw_date",
"type": "Datetime",
"datetimeOptions": {
"localeCode": "en-GB"
},
"createColumn": true,
"filter": {
"expression": "(after :date1)",
"valuesMap": [
{
"valueReference": ":date1",
"value": "2022-03-08T00:00:00.000Z"
}
]
}
}
}
]
}
}
Demo

AppSync: Unable to convert to object

I'm getting this error in AppSync while trying to create a mutation:
{
"data": {
"sendMessage": null
},
"errors": [
{
"path": [
"sendMessage"
],
"data": null,
"errorType": "MappingTemplate",
"errorInfo": null,
"locations": [
{
"line": 2,
"column": 3,
"sourceName": null
}
],
"message": "Unable to convert {dealId=312321312312, from=312321313, to=312321312312, id=7e7a8ba9-8e68-425a-9352-4421706c728a, type=text, body=Teste 122, status=sent} to Object."
}
]
}
The mapping template:
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
"id": $util.dynamodb.toDynamoDBJson($util.autoId()),
},
"attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args.input)
}
And the Mutation:
mutation MutationTest {
sendMessage(input: {dealId: "312321312312", from: "312321313", type: "text", to: "312321312312", status: "sent", body: "Teste 122"})
}
Thoughts? Thanks!
I think you need at least one return value, so your mutation would look like this:
mutation MutationTest {
sendMessage(input: {dealId: "312321312312", from: "312321313", type: "text", to: "312321312312", status: "sent", body: "Teste 122"}){
dealID
}
}

Count number of times an object key occurs grouped by other params in ElasticSearch

I have the following documents in ES:
[
{
"endpoint": "/abc",
"user": "John",
"method": "GET",
"params": {
"param1": 1,
"param2": 2
}
},
{
"endpoint": "/abc",
"user": "John",
"method": "GET",
"params": {
"param1": 4,
"param2": 5,
"param3": 100
}
},
{
"endpoint": "/xyz",
"user": "Jimmy",
"method": "POST",
"params": {
"param1": 99,
"param2": 88,
"param4": 65
}
},
{
"endpoint": "/xyz",
"user": "Jimmy",
"method": "POST",
"params": {
"param1": 4,
"param2": 2,
"param5": 3
}
}
]
I want to perform a count aggregation grouped by (endpoint, user, method, param_name) where param_name is the keys of params object. So, the aggregation for the above set of documents would be:
endpoint: /abc, user: John, method: GET, param1: 2 ( since param1 is used 2 times by user John on endpoint /abc with method GET)
endpoint: /abc, user: John, method: GET, param2: 2
endpoint: /abc, user: John, method: GET, param3: 1
endpoint: /xyz, user: Jimmy, method: POST, param1: 2
endpoint: /xyz, user: Jimmy, method: POST, param2: 2
endpoint: /xyz, user: Jimmy, method: POST, param4: 1
endpoint: /xyz, user: Jimmy, method: POST, param5: 1
Any help on how to solve this is much appreciated!
If your mapping looks like this (collapsed for brevity):
{"groups":{"mappings":{"properties":{"endpoint":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"groups":{"type":"nested","properties":{"group_id":{"type":"long"},"parent_group_id":{"type":"long"},"parent_group_title":{"type":"text","term_vector":"with_positions_offsets","fields":{"keyword":{"type":"keyword"}},"analyzer":"my_custom_analyzer"},"title":{"type":"text","term_vector":"with_positions_offsets","fields":{"keyword":{"type":"keyword"}},"analyzer":"my_custom_analyzer"}}},"method":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"params":{"properties":{"param1":{"type":"long"},"param2":{"type":"long"},"param3":{"type":"long"},"param4":{"type":"long"},"param5":{"type":"long"}}},"user":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}}}}}}
you can leverage a bunch of chained terms aggs plus a scripted_metric to sum up the individual params' stats:
GET groups/_search
{
"size": 0,
"aggs": {
"by_endpoint": {
"terms": {
"field": "endpoint.keyword"
},
"aggs": {
"by_user": {
"terms": {
"field": "user.keyword"
},
"aggs": {
"by_method": {
"terms": {
"field": "method.keyword"
},
"aggs": {
"by_params": {
"scripted_metric": {
"init_script": "state.params_map=[:]",
"map_script": """
def param_keys = ['param1', 'param2', 'param3', 'param4', 'param5'];
for (def key : param_keys) {
def param_path = 'params.' + key;
if (!doc.containsKey(param_path) || doc[param_path].size() == 0) return;
def param = doc[param_path].value + '';
if (state.params_map.containsKey(key)) {
state.params_map[key] += 1;
} else {
state.params_map[key] = 1;
}
}
""",
"combine_script": "return state",
"reduce_script": "return states"
}
}
}
}
}
}
}
}
}
}
yielding
...
{
"key":"/abc",
"doc_count":2,
"by_user":{
"doc_count_error_upper_bound":0,
"sum_other_doc_count":0,
"buckets":[
{
"key":"John",
"doc_count":2,
"by_method":{
"doc_count_error_upper_bound":0,
"sum_other_doc_count":0,
"buckets":[
{
"key":"GET",
"doc_count":2,
"by_params":{
"value":[
{
"params_map":{
"param3":1,
"param1":2,
"param2":2
}
}
]
}
}
]
}
}
]
}
}
...
which can be quite easily post-processed into the csv-ish format you've got above.

Date math in elastic watcher email

I would like to find the datetime for 1 day ago so that I can create link to kibana in an email sent from the watcher. Using Elasticsearch 5.0.2
I've tried the watch below but it returns an error of
ScriptException[runtime error]; nested: IllegalArgumentException[Unable to find dynamic method [minusDays] with [1] arguments for class [org.joda.time.DateTime].];
minusDays does exist in the joda DateTime spec
but it doesn't exist in the elastic codebase
here's the watch
PUT /_xpack/watcher/watch/errors-prod
{
"trigger": {
"schedule": {
"daily": {
"at": [
"08:36"
]
}
}
},
"input": {
"search": {
"request": {
"search_type": "query_then_fetch",
"indices": [
"<das-logstash-{now}>",
"<das-logstash-{now-1d}>"
],
"types": [
"redis-input"
],
"body": {
"size": 0,
"query": {
"match_all": {}
}
}
}
}
},
"actions": {
"send_email": {
"transform": {
"script" : "return [ 'from' : ctx.trigger.scheduled_time.minusDays(1) ]"
},
"email": {
"profile": "standard",
"from": "noreply#email.com",
"to": [
"me#email.com"
],
"subject": "errors",
"body": {
"html": "<html><body><p>from {{ctx.payload.from}}</p><p>to {{ctx.trigger.scheduled_time}}</p></body></html>"
}
}
}
}
}
I needed something similar and was able to hack this together by modifying a comment that almost worked from an elastic forum.
"transform": {
"script" : {
"source" : "def payload = ctx.payload; DateFormat df = new SimpleDateFormat(\"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'\"); ctx.payload.from = df.format(Date.from(Instant.ofEpochMilli(ctx.execution_time.getMillis() - (24 * 60 * 60 * 1000) ))); return payload"
}
},
Hope that helps!

Resources