painless-examples.asciidoc 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  1. [role="xpack"]
  2. [[transform-painless-examples]]
  3. = Painless examples for {transforms}
  4. ++++
  5. <titleabbrev>Painless examples</titleabbrev>
  6. ++++
  7. These examples demonstrate how to use Painless in {transforms}. You can learn
  8. more about the Painless scripting language in the
  9. {painless}/painless-guide.html[Painless guide].
  10. * <<painless-top-hits>>
  11. * <<painless-time-features>>
  12. // * <<painless-group-by>>
  13. * <<painless-bucket-script>>
  14. * <<painless-count-http>>
  15. * <<painless-compare>>
  16. * <<painless-web-session>>
  17. [NOTE]
  18. --
  19. * While the context of the following examples is the {transform} use case,
  20. the Painless scripts in the snippets below can be used in other {es} search
  21. aggregations, too.
  22. * All the following examples use scripts, {transforms} cannot deduce mappings of
  23. output fields when the fields are created by a script. {transforms-cap} don't
  24. create any mappings in the destination index for these fields, which means they
  25. get dynamically mapped. Create the destination index prior to starting the
  26. {transform} in case you want explicit mappings.
  27. --
  28. [[painless-top-hits]]
  29. == Getting top hits by using scripted metric aggregation
  30. This snippet shows how to find the latest document, in other words the document
  31. with the latest timestamp. From a technical perspective, it helps to achieve
  32. the function of a <<search-aggregations-metrics-top-hits-aggregation>> by using
  33. scripted metric aggregation in a {transform}, which provides a metric output.
  34. [source,js]
  35. --------------------------------------------------
  36. "aggregations": {
  37. "latest_doc": {
  38. "scripted_metric": {
  39. "init_script": "state.timestamp_latest = 0L; state.last_doc = ''", <1>
  40. "map_script": """ <2>
  41. def current_date = doc['@timestamp'].getValue().toInstant().toEpochMilli();
  42. if (current_date > state.timestamp_latest)
  43. {state.timestamp_latest = current_date;
  44. state.last_doc = new HashMap(params['_source']);}
  45. """,
  46. "combine_script": "return state", <3>
  47. "reduce_script": """ <4>
  48. def last_doc = '';
  49. def timestamp_latest = 0L;
  50. for (s in states) {if (s.timestamp_latest > (timestamp_latest))
  51. {timestamp_latest = s.timestamp_latest; last_doc = s.last_doc;}}
  52. return last_doc
  53. """
  54. }
  55. }
  56. }
  57. --------------------------------------------------
  58. // NOTCONSOLE
  59. <1> The `init_script` creates a long type `timestamp_latest` and a string type
  60. `last_doc` in the `state` object.
  61. <2> The `map_script` defines `current_date` based on the timestamp of the
  62. document, then compares `current_date` with `state.timestamp_latest`, finally
  63. returns `state.last_doc` from the shard. By using `new HashMap(...)` you copy
  64. the source document, this is important whenever you want to pass the full source
  65. object from one phase to the next.
  66. <3> The `combine_script` returns `state` from each shard.
  67. <4> The `reduce_script` iterates through the value of `s.timestamp_latest`
  68. returned by each shard and returns the document with the latest timestamp
  69. (`last_doc`). In the response, the top hit (in other words, the `latest_doc`) is
  70. nested below the `latest_doc` field.
  71. Check the <<scripted-metric-aggregation-scope,scope of scripts>> for detailed
  72. explanation on the respective scripts.
  73. You can retrieve the last value in a similar way:
  74. [source,js]
  75. --------------------------------------------------
  76. "aggregations": {
  77. "latest_value": {
  78. "scripted_metric": {
  79. "init_script": "state.timestamp_latest = 0L; state.last_value = ''",
  80. "map_script": """
  81. def current_date = doc['@timestamp'].getValue().toInstant().toEpochMilli();
  82. if (current_date > state.timestamp_latest)
  83. {state.timestamp_latest = current_date;
  84. state.last_value = params['_source']['value'];}
  85. """,
  86. "combine_script": "return state",
  87. "reduce_script": """
  88. def last_value = '';
  89. def timestamp_latest = 0L;
  90. for (s in states) {if (s.timestamp_latest > (timestamp_latest))
  91. {timestamp_latest = s.timestamp_latest; last_value = s.last_value;}}
  92. return last_value
  93. """
  94. }
  95. }
  96. }
  97. --------------------------------------------------
  98. // NOTCONSOLE
  99. [discrete]
  100. [[top-hits-stored-scripts]]
  101. === Getting top hits by using stored scripts
  102. You can also use the power of
  103. {ref}/create-stored-script-api.html[stored scripts] to get the latest value.
  104. Stored scripts reduce compilation time, make searches faster, and are
  105. updatable.
  106. 1. Create the stored scripts:
  107. +
  108. --
  109. [source,js]
  110. --------------------------------------------------
  111. POST _scripts/last-value-map-init
  112. {
  113. "script": {
  114. "lang": "painless",
  115. "source": """
  116. state.timestamp_latest = 0L; state.last_value = ''
  117. """
  118. }
  119. }
  120. POST _scripts/last-value-map
  121. {
  122. "script": {
  123. "lang": "painless",
  124. "source": """
  125. def current_date = doc['@timestamp'].getValue().toInstant().toEpochMilli();
  126. if (current_date > state.timestamp_latest)
  127. {state.timestamp_latest = current_date;
  128. state.last_value = doc[params['key']].value;}
  129. """
  130. }
  131. }
  132. POST _scripts/last-value-combine
  133. {
  134. "script": {
  135. "lang": "painless",
  136. "source": """
  137. return state
  138. """
  139. }
  140. }
  141. POST _scripts/last-value-reduce
  142. {
  143. "script": {
  144. "lang": "painless",
  145. "source": """
  146. def last_value = '';
  147. def timestamp_latest = 0L;
  148. for (s in states) {if (s.timestamp_latest > (timestamp_latest))
  149. {timestamp_latest = s.timestamp_latest; last_value = s.last_value;}}
  150. return last_value
  151. """
  152. }
  153. }
  154. --------------------------------------------------
  155. // NOTCONSOLE
  156. --
  157. 2. Use the stored scripts in a scripted metric aggregation.
  158. +
  159. --
  160. [source,js]
  161. --------------------------------------------------
  162. "aggregations":{
  163. "latest_value":{
  164. "scripted_metric":{
  165. "init_script":{
  166. "id":"last-value-map-init"
  167. },
  168. "map_script":{
  169. "id":"last-value-map",
  170. "params":{
  171. "key":"field_with_last_value" <1>
  172. }
  173. },
  174. "combine_script":{
  175. "id":"last-value-combine"
  176. },
  177. "reduce_script":{
  178. "id":"last-value-reduce"
  179. }
  180. --------------------------------------------------
  181. // NOTCONSOLE
  182. <1> The parameter `field_with_last_value` can be set any field that you want the
  183. latest value for.
  184. --
  185. [[painless-time-features]]
  186. == Getting time features by using aggregations
  187. This snippet shows how to extract time based features by using Painless in a
  188. {transform}. The snippet uses an index where `@timestamp` is defined as a `date`
  189. type field.
  190. [source,js]
  191. --------------------------------------------------
  192. "aggregations": {
  193. "avg_hour_of_day": { <1>
  194. "avg":{
  195. "script": { <2>
  196. "source": """
  197. ZonedDateTime date = doc['@timestamp'].value; <3>
  198. return date.getHour(); <4>
  199. """
  200. }
  201. }
  202. },
  203. "avg_month_of_year": { <5>
  204. "avg":{
  205. "script": { <6>
  206. "source": """
  207. ZonedDateTime date = doc['@timestamp'].value; <7>
  208. return date.getMonthValue(); <8>
  209. """
  210. }
  211. }
  212. },
  213. ...
  214. }
  215. --------------------------------------------------
  216. // NOTCONSOLE
  217. <1> Name of the aggregation.
  218. <2> Contains the Painless script that returns the hour of the day.
  219. <3> Sets `date` based on the timestamp of the document.
  220. <4> Returns the hour value from `date`.
  221. <5> Name of the aggregation.
  222. <6> Contains the Painless script that returns the month of the year.
  223. <7> Sets `date` based on the timestamp of the document.
  224. <8> Returns the month value from `date`.
  225. ////
  226. [[painless-group-by]]
  227. == Using Painless in `group_by`
  228. It is possible to base the `group_by` property of a {transform} on the output of
  229. a script. The following example uses the {kib} sample web logs dataset. The goal
  230. here is to make the {transform} output easier to understand through normalizing
  231. the value of the fields that the data is grouped by.
  232. [source,console]
  233. --------------------------------------------------
  234. POST _transform/_preview
  235. {
  236. "source": {
  237. "index": [ <1>
  238. "kibana_sample_data_logs"
  239. ]
  240. },
  241. "pivot": {
  242. "group_by": {
  243. "agent": {
  244. "terms": {
  245. "script": { <2>
  246. "source": """String agent = doc['agent.keyword'].value;
  247. if (agent.contains("MSIE")) {
  248. return "internet explorer";
  249. } else if (agent.contains("AppleWebKit")) {
  250. return "safari";
  251. } else if (agent.contains('Firefox')) {
  252. return "firefox";
  253. } else { return agent }""",
  254. "lang": "painless"
  255. }
  256. }
  257. }
  258. },
  259. "aggregations": { <3>
  260. "200": {
  261. "filter": {
  262. "term": {
  263. "response": "200"
  264. }
  265. }
  266. },
  267. "404": {
  268. "filter": {
  269. "term": {
  270. "response": "404"
  271. }
  272. }
  273. },
  274. "503": {
  275. "filter": {
  276. "term": {
  277. "response": "503"
  278. }
  279. }
  280. }
  281. }
  282. },
  283. "dest": { <4>
  284. "index": "pivot_logs"
  285. }
  286. }
  287. --------------------------------------------------
  288. // TEST[skip:setup kibana sample data]
  289. <1> Specifies the source index or indices.
  290. <2> The script defines an `agent` string based on the `agent` field of the
  291. documents, then iterates through the values. If an `agent` field contains
  292. "MSIE", than the script returns "Internet Explorer". If it contains
  293. `AppleWebKit`, it returns "safari". It returns "firefox" if the field value
  294. contains "Firefox". Finally, in every other case, the value of the field is
  295. returned.
  296. <3> The aggregations object contains filters that narrow down the results to
  297. documents that contains `200`, `404`, or `503` values in the `response` field.
  298. <4> Specifies the destination index of the {transform}.
  299. The API returns the following result:
  300. [source,js]
  301. --------------------------------------------------
  302. {
  303. "preview" : [
  304. {
  305. "agent" : "firefox",
  306. "200" : 4931,
  307. "404" : 259,
  308. "503" : 172
  309. },
  310. {
  311. "agent" : "internet explorer",
  312. "200" : 3674,
  313. "404" : 210,
  314. "503" : 126
  315. },
  316. {
  317. "agent" : "safari",
  318. "200" : 4227,
  319. "404" : 332,
  320. "503" : 143
  321. }
  322. ],
  323. "mappings" : {
  324. "properties" : {
  325. "200" : {
  326. "type" : "long"
  327. },
  328. "agent" : {
  329. "type" : "keyword"
  330. },
  331. "404" : {
  332. "type" : "long"
  333. },
  334. "503" : {
  335. "type" : "long"
  336. }
  337. }
  338. }
  339. }
  340. --------------------------------------------------
  341. // NOTCONSOLE
  342. You can see that the `agent` values are simplified so it is easier to interpret
  343. them. The table below shows how normalization modifies the output of the
  344. {transform} in our example compared to the non-normalized values.
  345. [width="50%"]
  346. |===
  347. | Non-normalized `agent` value | Normalized `agent` value
  348. | "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)" | "internet explorer"
  349. | "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24" | "safari"
  350. | "Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1" | "firefox"
  351. |===
  352. ////
  353. [[painless-bucket-script]]
  354. == Getting duration by using bucket script
  355. This example shows you how to get the duration of a session by client IP from a
  356. data log by using
  357. <<search-aggregations-pipeline-bucket-script-aggregation,bucket script>>.
  358. The example uses the {kib} sample web logs dataset.
  359. [source,console]
  360. --------------------------------------------------
  361. PUT _transform/data_log
  362. {
  363. "source": {
  364. "index": "kibana_sample_data_logs"
  365. },
  366. "dest": {
  367. "index": "data-logs-by-client"
  368. },
  369. "pivot": {
  370. "group_by": {
  371. "machine.os": {"terms": {"field": "machine.os.keyword"}},
  372. "machine.ip": {"terms": {"field": "clientip"}}
  373. },
  374. "aggregations": {
  375. "time_frame.lte": {
  376. "max": {
  377. "field": "timestamp"
  378. }
  379. },
  380. "time_frame.gte": {
  381. "min": {
  382. "field": "timestamp"
  383. }
  384. },
  385. "time_length": { <1>
  386. "bucket_script": {
  387. "buckets_path": { <2>
  388. "min": "time_frame.gte.value",
  389. "max": "time_frame.lte.value"
  390. },
  391. "script": "params.max - params.min" <3>
  392. }
  393. }
  394. }
  395. }
  396. }
  397. --------------------------------------------------
  398. // TEST[skip:setup kibana sample data]
  399. <1> To define the length of the sessions, we use a bucket script.
  400. <2> The bucket path is a map of script variables and their associated path to
  401. the buckets you want to use for the variable. In this particular case, `min` and
  402. `max` are variables mapped to `time_frame.gte.value` and `time_frame.lte.value`.
  403. <3> Finally, the script substracts the start date of the session from the end
  404. date which results in the duration of the session.
  405. [[painless-count-http]]
  406. == Counting HTTP responses by using scripted metric aggregation
  407. You can count the different HTTP response types in a web log data set by using
  408. scripted metric aggregation as part of the {transform}. You can achieve a
  409. similar function with filter aggregations, check the
  410. {ref}/transform-examples.html#example-clientips[Finding suspicious client IPs]
  411. example for details.
  412. The example below assumes that the HTTP response codes are stored as keywords in
  413. the `response` field of the documents.
  414. [source,js]
  415. --------------------------------------------------
  416. "aggregations": { <1>
  417. "responses.counts": { <2>
  418. "scripted_metric": { <3>
  419. "init_script": "state.responses = ['error':0L,'success':0L,'other':0L]", <4>
  420. "map_script": """ <5>
  421. def code = doc['response.keyword'].value;
  422. if (code.startsWith('5') || code.startsWith('4')) {
  423. state.responses.error += 1 ;
  424. } else if(code.startsWith('2')) {
  425. state.responses.success += 1;
  426. } else {
  427. state.responses.other += 1;
  428. }
  429. """,
  430. "combine_script": "state.responses", <6>
  431. "reduce_script": """ <7>
  432. def counts = ['error': 0L, 'success': 0L, 'other': 0L];
  433. for (responses in states) {
  434. counts.error += responses['error'];
  435. counts.success += responses['success'];
  436. counts.other += responses['other'];
  437. }
  438. return counts;
  439. """
  440. }
  441. },
  442. ...
  443. }
  444. --------------------------------------------------
  445. // NOTCONSOLE
  446. <1> The `aggregations` object of the {transform} that contains all aggregations.
  447. <2> Object of the `scripted_metric` aggregation.
  448. <3> This `scripted_metric` performs a distributed operation on the web log data
  449. to count specific types of HTTP responses (error, success, and other).
  450. <4> The `init_script` creates a `responses` array in the `state` object with
  451. three properties (`error`, `success`, `other`) with long data type.
  452. <5> The `map_script` defines `code` based on the `response.keyword` value of the
  453. document, then it counts the errors, successes, and other responses based on the
  454. first digit of the responses.
  455. <6> The `combine_script` returns `state.responses` from each shard.
  456. <7> The `reduce_script` creates a `counts` array with the `error`, `success`,
  457. and `other` properties, then iterates through the value of `responses` returned
  458. by each shard and assigns the different response types to the appropriate
  459. properties of the `counts` object; error responses to the error counts, success
  460. responses to the success counts, and other responses to the other counts.
  461. Finally, returns the `counts` array with the response counts.
  462. [[painless-compare]]
  463. == Comparing indices by using scripted metric aggregations
  464. This example shows how to compare the content of two indices by a {transform}
  465. that uses a scripted metric aggregation.
  466. [source,console]
  467. --------------------------------------------------
  468. POST _transform/_preview
  469. {
  470. "id" : "index_compare",
  471. "source" : { <1>
  472. "index" : [
  473. "index1",
  474. "index2"
  475. ],
  476. "query" : {
  477. "match_all" : { }
  478. }
  479. },
  480. "dest" : { <2>
  481. "index" : "compare"
  482. },
  483. "pivot" : {
  484. "group_by" : {
  485. "unique-id" : {
  486. "terms" : {
  487. "field" : "<unique-id-field>" <3>
  488. }
  489. }
  490. },
  491. "aggregations" : {
  492. "compare" : { <4>
  493. "scripted_metric" : {
  494. "map_script" : "state.doc = new HashMap(params['_source'])", <5>
  495. "combine_script" : "return state", <6>
  496. "reduce_script" : """ <7>
  497. if (states.size() != 2) {
  498. return "count_mismatch"
  499. }
  500. if (states.get(0).equals(states.get(1))) {
  501. return "match"
  502. } else {
  503. return "mismatch"
  504. }
  505. """
  506. }
  507. }
  508. }
  509. }
  510. }
  511. --------------------------------------------------
  512. // TEST[skip:setup kibana sample data]
  513. <1> The indices referenced in the `source` object are compared to each other.
  514. <2> The `dest` index contains the results of the comparison.
  515. <3> The `group_by` field needs to be a unique identifier for each document.
  516. <4> Object of the `scripted_metric` aggregation.
  517. <5> The `map_script` defines `doc` in the state object. By using
  518. `new HashMap(...)` you copy the source document, this is important whenever you
  519. want to pass the full source object from one phase to the next.
  520. <6> The `combine_script` returns `state` from each shard.
  521. <7> The `reduce_script` checks if the size of the indices are equal. If they are
  522. not equal, than it reports back a `count_mismatch`. Then it iterates through all
  523. the values of the two indices and compare them. If the values are equal, then it
  524. returns a `match`, otherwise returns a `mismatch`.
  525. [[painless-web-session]]
  526. == Getting web session details by using scripted metric aggregation
  527. This example shows how to derive multiple features from a single transaction.
  528. Let's take a look on the example source document from the data:
  529. .Source document
  530. [%collapsible%open]
  531. =====
  532. [source,js]
  533. --------------------------------------------------
  534. {
  535. "_index":"apache-sessions",
  536. "_type":"_doc",
  537. "_id":"KvzSeGoB4bgw0KGbE3wP",
  538. "_score":1.0,
  539. "_source":{
  540. "@timestamp":1484053499256,
  541. "apache":{
  542. "access":{
  543. "sessionid":"571604f2b2b0c7b346dc685eeb0e2306774a63c2",
  544. "url":"http://www.leroymerlin.fr/v3/search/search.do?keyword=Carrelage%20salle%20de%20bain",
  545. "path":"/v3/search/search.do",
  546. "query":"keyword=Carrelage%20salle%20de%20bain",
  547. "referrer":"http://www.leroymerlin.fr/v3/p/produits/carrelage-parquet-sol-souple/carrelage-sol-et-mur/decor-listel-et-accessoires-carrelage-mural-l1308217717?resultOffset=0&resultLimit=51&resultListShape=MOSAIC&priceStyle=SALEUNIT_PRICE",
  548. "user_agent":{
  549. "original":"Mobile Safari 10.0 Mac OS X (iPad) Apple Inc.",
  550. "os_name":"Mac OS X (iPad)"
  551. },
  552. "remote_ip":"0337b1fa-5ed4-af81-9ef4-0ec53be0f45d",
  553. "geoip":{
  554. "country_iso_code":"FR",
  555. "location":{
  556. "lat":48.86,
  557. "lon":2.35
  558. }
  559. },
  560. "response_code":200,
  561. "method":"GET"
  562. }
  563. }
  564. }
  565. }
  566. ...
  567. --------------------------------------------------
  568. // NOTCONSOLE
  569. =====
  570. By using the `sessionid` as a group-by field, you are able to enumerate events
  571. through the session and get more details of the session by using scripted metric
  572. aggregation.
  573. [source,js]
  574. --------------------------------------------------
  575. POST _transform/_preview
  576. {
  577. "source": {
  578. "index": "apache-sessions"
  579. },
  580. "pivot": {
  581. "group_by": {
  582. "sessionid": { <1>
  583. "terms": {
  584. "field": "apache.access.sessionid"
  585. }
  586. }
  587. },
  588. "aggregations": { <2>
  589. "distinct_paths": {
  590. "cardinality": {
  591. "field": "apache.access.path"
  592. }
  593. },
  594. "num_pages_viewed": {
  595. "value_count": {
  596. "field": "apache.access.url"
  597. }
  598. },
  599. "session_details": {
  600. "scripted_metric": {
  601. "init_script": "state.docs = []", <3>
  602. "map_script": """ <4>
  603. Map span = [
  604. '@timestamp':doc['@timestamp'].value,
  605. 'url':doc['apache.access.url'].value,
  606. 'referrer':doc['apache.access.referrer'].value
  607. ];
  608. state.docs.add(span)
  609. """,
  610. "combine_script": "return state.docs;", <5>
  611. "reduce_script": """ <6>
  612. def all_docs = [];
  613. for (s in states) {
  614. for (span in s) {
  615. all_docs.add(span);
  616. }
  617. }
  618. all_docs.sort((HashMap o1, HashMap o2)->o1['@timestamp'].toEpochMilli().compareTo(o2['@timestamp'].toEpochMilli()));
  619. def size = all_docs.size();
  620. def min_time = all_docs[0]['@timestamp'];
  621. def max_time = all_docs[size-1]['@timestamp'];
  622. def duration = max_time.toEpochMilli() - min_time.toEpochMilli();
  623. def entry_page = all_docs[0]['url'];
  624. def exit_path = all_docs[size-1]['url'];
  625. def first_referrer = all_docs[0]['referrer'];
  626. def ret = new HashMap();
  627. ret['first_time'] = min_time;
  628. ret['last_time'] = max_time;
  629. ret['duration'] = duration;
  630. ret['entry_page'] = entry_page;
  631. ret['exit_path'] = exit_path;
  632. ret['first_referrer'] = first_referrer;
  633. return ret;
  634. """
  635. }
  636. }
  637. }
  638. }
  639. }
  640. --------------------------------------------------
  641. // NOTCONSOLE
  642. <1> The data is grouped by `sessionid`.
  643. <2> The aggregations counts the number of paths and enumerate the viewed pages
  644. during the session.
  645. <3> The `init_script` creates an array type `doc` in the `state` object.
  646. <4> The `map_script` defines a `span` array with a timestamp, a URL, and a
  647. referrer value which are based on the corresponding values of the document, then
  648. adds the value of the `span` array to the `doc` object.
  649. <5> The `combine_script` returns `state.docs` from each shard.
  650. <6> The `reduce_script` defines various objects like `min_time`, `max_time`, and
  651. `duration` based on the document fields, then declares a `ret` object, and
  652. copies the source document by using `new HashMap ()`. Next, the script defines
  653. `first_time`, `last_time`, `duration` and other fields inside the `ret` object
  654. based on the corresponding object defined earlier, finally returns `ret`.
  655. The API call results in a similar response:
  656. [source,js]
  657. --------------------------------------------------
  658. {
  659. "num_pages_viewed" : 2.0,
  660. "session_details" : {
  661. "duration" : 100300001,
  662. "first_referrer" : "https://www.bing.com/",
  663. "entry_page" : "http://www.leroymerlin.fr/v3/p/produits/materiaux-menuiserie/porte-coulissante-porte-interieure-escalier-et-rambarde/barriere-de-securite-l1308218463",
  664. "first_time" : "2017-01-10T21:22:52.982Z",
  665. "last_time" : "2017-01-10T21:25:04.356Z",
  666. "exit_path" : "http://www.leroymerlin.fr/v3/p/produits/materiaux-menuiserie/porte-coulissante-porte-interieure-escalier-et-rambarde/barriere-de-securite-l1308218463?__result-wrapper?pageTemplate=Famille%2FMat%C3%A9riaux+et+menuiserie&resultOffset=0&resultLimit=50&resultListShape=PLAIN&nomenclatureId=17942&priceStyle=SALEUNIT_PRICE&fcr=1&*4294718806=4294718806&*14072=14072&*4294718593=4294718593&*17942=17942"
  667. },
  668. "distinct_paths" : 1.0,
  669. "sessionid" : "000046f8154a80fd89849369c984b8cc9d795814"
  670. },
  671. {
  672. "num_pages_viewed" : 10.0,
  673. "session_details" : {
  674. "duration" : 343100405,
  675. "first_referrer" : "https://www.google.fr/",
  676. "entry_page" : "http://www.leroymerlin.fr/",
  677. "first_time" : "2017-01-10T16:57:39.937Z",
  678. "last_time" : "2017-01-10T17:03:23.049Z",
  679. "exit_path" : "http://www.leroymerlin.fr/v3/p/produits/porte-de-douche-coulissante-adena-e168578"
  680. },
  681. "distinct_paths" : 8.0,
  682. "sessionid" : "000087e825da1d87a332b8f15fa76116c7467da6"
  683. }
  684. ...
  685. --------------------------------------------------
  686. // NOTCONSOLE