From a8df98259085fb7a74fb1d6543133780e994e0cd Mon Sep 17 00:00:00 2001
From: uboness <uboness@gmail.com>
Date: Fri, 30 Mar 2018 16:56:47 +0200
Subject: [PATCH] [SearchBar] Added support for emitting a Query as an
 Elasticsearch query string (#598)

Example:

```
const query = Query.parse(`john group:(eng or "marketing org") -date:'2004-03' -is:active`);
console.log(Query.toEsQueryString(query));
```

the above logs:

```
+john +(group:eng OR group:"marketing org") -date:(>=2004-03 AND <2004-04) +active:false
```
---
 CHANGELOG.md                                  |   1 +
 src-docs/src/views/search_bar/search_bar.js   |  25 ++-
 ....snap => ast_to_es_query_dsl.test.js.snap} |  24 +--
 .../ast_to_es_query_string.test.js.snap       |  25 +++
 .../{ast_to_es.js => ast_to_es_query_dsl.js}  |   2 +-
 ...es.test.js => ast_to_es_query_dsl.test.js} |  28 ++--
 .../query/ast_to_es_query_string.js           | 145 ++++++++++++++++++
 .../query/ast_to_es_query_string.test.js      | 112 ++++++++++++++
 .../search_bar/query/date_format.js           |  12 +-
 src/components/search_bar/query/query.js      |  10 +-
 10 files changed, 347 insertions(+), 37 deletions(-)
 rename src/components/search_bar/query/__snapshots__/{ast_to_es.test.js.snap => ast_to_es_query_dsl.test.js.snap} (85%)
 create mode 100644 src/components/search_bar/query/__snapshots__/ast_to_es_query_string.test.js.snap
 rename src/components/search_bar/query/{ast_to_es.js => ast_to_es_query_dsl.js} (99%)
 rename src/components/search_bar/query/{ast_to_es.test.js => ast_to_es_query_dsl.test.js} (81%)
 create mode 100644 src/components/search_bar/query/ast_to_es_query_string.js
 create mode 100644 src/components/search_bar/query/ast_to_es_query_string.test.js

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 45c0b57e2ca..542c36412e6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
 
 - Relaxed query syntax of `EuiSearchBar` to allow usage of hyphens without escaping ([#581](https://github.com/elastic/eui/pull/581))
 - Added support for range queries in `EuiSearchBar` (works for numeric and date values) ([#485](https://github.com/elastic/eui/pull/485))
+- Added support for emitting a `EuiSearchBar` query to an Elasticsearch query string ([#598](https://github.com/elastic/eui/pull/598))
 - Add support for expandable rows to `EuiBasicTable` ([#585](https://github.com/elastic/eui/pull/585))
 
 # [`0.0.35`](https://github.com/elastic/eui/tree/v0.0.35)
diff --git a/src-docs/src/views/search_bar/search_bar.js b/src-docs/src/views/search_bar/search_bar.js
index 3313019a248..c61c86a2d71 100644
--- a/src-docs/src/views/search_bar/search_bar.js
+++ b/src-docs/src/views/search_bar/search_bar.js
@@ -151,6 +151,12 @@ export class SearchBar extends Component {
         stars: {
           type: 'number'
         },
+        created: {
+          type: 'date'
+        },
+        owner: {
+          type: 'string'
+        },
         tag: {
           type: 'string',
           validate: (value) => {
@@ -251,20 +257,31 @@ export class SearchBar extends Component {
       query,
     } = this.state;
 
-    const esQuery = EuiSearchBar.Query.toESQuery(query);
+    const esQueryDsl = EuiSearchBar.Query.toESQuery(query);
+    const esQueryString = EuiSearchBar.Query.toESQueryString(query);
 
     const content = this.renderError() || (
       <EuiFlexGroup>
         <EuiFlexItem grow={4}>
+
           <EuiTitle size="s">
-            <h3>Elasticsearch query</h3>
+            <h3>Elasticsearch Query String</h3>
           </EuiTitle>
-
           <EuiSpacer size="s"/>
+          <EuiCodeBlock language="js">
+            {esQueryString ? esQueryString : ''}
+          </EuiCodeBlock>
+
+          <EuiSpacer size="l"/>
 
+          <EuiTitle size="s">
+            <h3>Elasticsearch Query DSL</h3>
+          </EuiTitle>`
+          <EuiSpacer size="s"/>
           <EuiCodeBlock language="js">
-            {esQuery ? JSON.stringify(esQuery, null, 2) : ''}
+            {esQueryDsl ? JSON.stringify(esQueryDsl, null, 2) : ''}
           </EuiCodeBlock>
+
         </EuiFlexItem>
 
         <EuiFlexItem grow={6}>
diff --git a/src/components/search_bar/query/__snapshots__/ast_to_es.test.js.snap b/src/components/search_bar/query/__snapshots__/ast_to_es_query_dsl.test.js.snap
similarity index 85%
rename from src/components/search_bar/query/__snapshots__/ast_to_es.test.js.snap
rename to src/components/search_bar/query/__snapshots__/ast_to_es_query_dsl.test.js.snap
index f59884cf1a3..ee9cc7a44d8 100644
--- a/src/components/search_bar/query/__snapshots__/ast_to_es.test.js.snap
+++ b/src/components/search_bar/query/__snapshots__/ast_to_es_query_dsl.test.js.snap
@@ -1,12 +1,12 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`astToEs ast - '' 1`] = `
+exports[`astToEsQueryDsl ast - '' 1`] = `
 Object {
   "match_all": Object {},
 }
 `;
 
-exports[`astToEs ast - '-group:es group:kibana -group:beats group:logstash' 1`] = `
+exports[`astToEsQueryDsl ast - '-group:es group:kibana -group:beats group:logstash' 1`] = `
 Object {
   "bool": Object {
     "must": Array [
@@ -33,7 +33,7 @@ Object {
 }
 `;
 
-exports[`astToEs ast - 'is:online group:kibana john' 1`] = `
+exports[`astToEsQueryDsl ast - 'is:online group:kibana john' 1`] = `
 Object {
   "bool": Object {
     "must": Array [
@@ -60,7 +60,7 @@ Object {
 }
 `;
 
-exports[`astToEs ast - 'john -doe is:online group:eng group:es -group:kibana -is:active' 1`] = `
+exports[`astToEsQueryDsl ast - 'john -doe is:online group:eng group:es -group:kibana -is:active' 1`] = `
 Object {
   "bool": Object {
     "must": Array [
@@ -107,7 +107,7 @@ Object {
 }
 `;
 
-exports[`astToEs ast - 'john -sales' 1`] = `
+exports[`astToEsQueryDsl ast - 'john -sales' 1`] = `
 Object {
   "bool": Object {
     "must": Array [
@@ -128,7 +128,7 @@ Object {
 }
 `;
 
-exports[`astToEs ast - 'john group:(eng or "marketing org") -group:"kibana team" 1`] = `
+exports[`astToEsQueryDsl ast - 'john group:(eng or "marketing org") -group:"kibana team" 1`] = `
 Object {
   "bool": Object {
     "must": Array [
@@ -168,7 +168,7 @@ Object {
 }
 `;
 
-exports[`astToEs ast - 'john group:(eng or es) -group:kibana' 1`] = `
+exports[`astToEsQueryDsl ast - 'john group:(eng or es) -group:kibana' 1`] = `
 Object {
   "bool": Object {
     "must": Array [
@@ -200,7 +200,7 @@ Object {
 }
 `;
 
-exports[`astToEs ast - -count<=4 size<5 age>=3 -number>9 1`] = `
+exports[`astToEsQueryDsl ast - -count<=4 size<5 age>=3 -number>9 1`] = `
 Object {
   "bool": Object {
     "must": Array [
@@ -239,7 +239,7 @@ Object {
 }
 `;
 
-exports[`astToEs ast - count>3 1`] = `
+exports[`astToEsQueryDsl ast - count>3 1`] = `
 Object {
   "bool": Object {
     "must": Array [
@@ -255,7 +255,7 @@ Object {
 }
 `;
 
-exports[`astToEs ast - date:'2004-03' -date<'2004-03-10' 1`] = `
+exports[`astToEsQueryDsl ast - date:'2004-03' -date<'2004-03-10' 1`] = `
 Object {
   "bool": Object {
     "must": Array [
@@ -278,7 +278,7 @@ Object {
 }
 `;
 
-exports[`astToEs ast - date>'2004-02' -otherDate>='2004-03-10' 1`] = `
+exports[`astToEsQueryDsl ast - date>'2004-02' -otherDate>='2004-03-10' 1`] = `
 Object {
   "bool": Object {
     "must": Array [
@@ -303,7 +303,7 @@ Object {
 }
 `;
 
-exports[`astToEs ast - date>='2004-03-22' 1`] = `
+exports[`astToEsQueryDsl ast - date>='2004-03-22' 1`] = `
 Object {
   "bool": Object {
     "must": Array [
diff --git a/src/components/search_bar/query/__snapshots__/ast_to_es_query_string.test.js.snap b/src/components/search_bar/query/__snapshots__/ast_to_es_query_string.test.js.snap
new file mode 100644
index 00000000000..dfad145a19b
--- /dev/null
+++ b/src/components/search_bar/query/__snapshots__/ast_to_es_query_string.test.js.snap
@@ -0,0 +1,25 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`astToEsQueryString ast - '' 1`] = `""`;
+
+exports[`astToEsQueryString ast - '-group:es group:kibana -group:beats group:logstash' 1`] = `"-group:es +group:kibana -group:beats +group:logstash"`;
+
+exports[`astToEsQueryString ast - 'is:online group:kibana john' 1`] = `"+online:true +group:kibana +john"`;
+
+exports[`astToEsQueryString ast - 'john -doe is:online group:eng group:es -group:kibana -is:active' 1`] = `"+john -doe +online:true +group:eng +group:es -group:kibana +active:false"`;
+
+exports[`astToEsQueryString ast - 'john -sales' 1`] = `"+john -sales"`;
+
+exports[`astToEsQueryString ast - 'john group:(eng or "marketing org") -group:"kibana team" 1`] = `"+john +(group:eng OR group:\\"marketing org\\") -group:\\"kibana team\\""`;
+
+exports[`astToEsQueryString ast - 'john group:(eng or es) -group:kibana' 1`] = `"+john +(group:eng OR group:es) -group:kibana"`;
+
+exports[`astToEsQueryString ast - -count<=4 size<5 age>=3 -number>9 1`] = `"-count:<=4 +size:<5 +age:>=3 -number:>9"`;
+
+exports[`astToEsQueryString ast - count>3 1`] = `"+count:>3"`;
+
+exports[`astToEsQueryString ast - date:'2004-03' -date<'2004-03-10' 1`] = `"+date:(>=2004-03 AND <2004-04) -date:<2004-03-10"`;
+
+exports[`astToEsQueryString ast - date>'2004-02' -otherDate>='2004-03-10' 1`] = `"+date:>=2004-03 -date:>=2004-03-10"`;
+
+exports[`astToEsQueryString ast - date>='2004-03-22' 1`] = `"+date:>=2004-03-22"`;
diff --git a/src/components/search_bar/query/ast_to_es.js b/src/components/search_bar/query/ast_to_es_query_dsl.js
similarity index 99%
rename from src/components/search_bar/query/ast_to_es.js
rename to src/components/search_bar/query/ast_to_es_query_dsl.js
index ad263b4ff4e..7e618193dd4 100644
--- a/src/components/search_bar/query/ast_to_es.js
+++ b/src/components/search_bar/query/ast_to_es_query_dsl.js
@@ -180,7 +180,7 @@ const collectFields = (ast) => {
   });
 };
 
-export const astToEs = (ast, options = {}) => {
+export const astToEsQueryDsl = (ast, options = {}) => {
 
   if (ast.clauses.length === 0) {
     return { match_all: {} };
diff --git a/src/components/search_bar/query/ast_to_es.test.js b/src/components/search_bar/query/ast_to_es_query_dsl.test.js
similarity index 81%
rename from src/components/search_bar/query/ast_to_es.test.js
rename to src/components/search_bar/query/ast_to_es_query_dsl.test.js
index 8c7eac409c9..b18e26f5ae3 100644
--- a/src/components/search_bar/query/ast_to_es.test.js
+++ b/src/components/search_bar/query/ast_to_es_query_dsl.test.js
@@ -1,18 +1,18 @@
-import { astToEs } from './ast_to_es';
 import { AST } from './ast';
 import moment from 'moment/moment';
 import { dateValue } from './date_value';
 import { Granularity } from './date_format';
+import { astToEsQueryDsl } from './ast_to_es_query_dsl';
 
-describe('astToEs', () => {
+describe('astToEsQueryDsl', () => {
 
   test(`ast - ''`, () => {
-    const query = astToEs(AST.create([]));
+    const query = astToEsQueryDsl(AST.create([]));
     expect(query).toMatchSnapshot();
   });
 
   test(`ast - 'john -sales'`, () => {
-    const query = astToEs(AST.create([
+    const query = astToEsQueryDsl(AST.create([
       AST.Term.must('john'),
       AST.Term.mustNot('sales'),
     ]));
@@ -20,7 +20,7 @@ describe('astToEs', () => {
   });
 
   test(`ast - '-group:es group:kibana -group:beats group:logstash'`, () => {
-    const query = astToEs(AST.create([
+    const query = astToEsQueryDsl(AST.create([
       AST.Field.mustNot.eq('group', 'es'),
       AST.Field.must.eq('group', 'kibana'),
       AST.Field.mustNot.eq('group', 'beats'),
@@ -30,7 +30,7 @@ describe('astToEs', () => {
   });
 
   test(`ast - 'is:online group:kibana john'`, () => {
-    const query = astToEs(AST.create([
+    const query = astToEsQueryDsl(AST.create([
       AST.Is.must('online'),
       AST.Field.must.eq('group', 'kibana'),
       AST.Term.must('john')
@@ -39,7 +39,7 @@ describe('astToEs', () => {
   });
 
   test(`ast - 'john -doe is:online group:eng group:es -group:kibana -is:active'`, () => {
-    const query = astToEs(AST.create([
+    const query = astToEsQueryDsl(AST.create([
       AST.Term.must('john'),
       AST.Term.mustNot('doe'),
       AST.Is.must('online'),
@@ -52,7 +52,7 @@ describe('astToEs', () => {
   });
 
   test(`ast - 'john group:(eng or es) -group:kibana'`, () => {
-    const query = astToEs(AST.create([
+    const query = astToEsQueryDsl(AST.create([
       AST.Term.must('john'),
       AST.Field.must.eq('group', ['eng', 'es']),
       AST.Field.mustNot.eq('group', 'kibana')
@@ -61,7 +61,7 @@ describe('astToEs', () => {
   });
 
   test(`ast - 'john group:(eng or "marketing org") -group:"kibana team"`, () => {
-    const query = astToEs(AST.create([
+    const query = astToEsQueryDsl(AST.create([
       AST.Term.must('john'),
       AST.Field.must.eq('group', ['eng', 'marketing org']),
       AST.Field.mustNot.eq('group', 'kibana team')
@@ -70,14 +70,14 @@ describe('astToEs', () => {
   });
 
   test(`ast - count>3`, () => {
-    const query = astToEs(AST.create([
+    const query = astToEsQueryDsl(AST.create([
       AST.Field.must.gt('count', 3)
     ]));
     expect(query).toMatchSnapshot();
   });
 
   test(`ast - -count<=4 size<5 age>=3 -number>9`, () => {
-    const query = astToEs(AST.create([
+    const query = astToEsQueryDsl(AST.create([
       AST.Field.mustNot.lte('count', 4),
       AST.Field.must.lt('size', 5),
       AST.Field.must.gte('age', 3),
@@ -87,14 +87,14 @@ describe('astToEs', () => {
   });
 
   test(`ast - date>='2004-03-22'`, () => {
-    const query = astToEs(AST.create([
+    const query = astToEsQueryDsl(AST.create([
       AST.Field.must.gte('date', dateValue(moment.utc('2004-03-22'), Granularity.DAY))
     ]));
     expect(query).toMatchSnapshot();
   });
 
   test(`ast - date:'2004-03' -date<'2004-03-10'`, () => {
-    const query = astToEs(AST.create([
+    const query = astToEsQueryDsl(AST.create([
       AST.Field.must.eq('date', dateValue(moment.utc('2004-03'), Granularity.MONTH)),
       AST.Field.mustNot.lt('date', dateValue(moment.utc('2004-03-10'), Granularity.DAY))
     ]));
@@ -102,7 +102,7 @@ describe('astToEs', () => {
   });
 
   test(`ast - date>'2004-02' -otherDate>='2004-03-10'`, () => {
-    const query = astToEs(AST.create([
+    const query = astToEsQueryDsl(AST.create([
       AST.Field.must.gt('date', dateValue(moment.utc('2004-02'), Granularity.MONTH)),
       AST.Field.mustNot.gte('date', dateValue(moment.utc('2004-03-10'), Granularity.DAY))
     ]));
diff --git a/src/components/search_bar/query/ast_to_es_query_string.js b/src/components/search_bar/query/ast_to_es_query_string.js
new file mode 100644
index 00000000000..0b213087c65
--- /dev/null
+++ b/src/components/search_bar/query/ast_to_es_query_string.js
@@ -0,0 +1,145 @@
+import { printIso8601 } from './date_format';
+import { isDateValue } from './date_value';
+import { AST, Operator } from './ast';
+import { isArray, isDateLike, isString, isBoolean, isNumber } from '../../../services/predicate';
+
+const emitMatch = (match) => {
+  if (!match) {
+    return '';
+  }
+  return AST.Match.isMust(match) ? '+' : '-';
+};
+
+const emitFieldDateLikeClause = (field, value, operator, match) => {
+  const matchOp = emitMatch(match);
+  switch (operator) {
+    case Operator.EQ:
+      return `${matchOp}${field}:${printIso8601(value)}`;
+    case Operator.GT:
+      return `${matchOp}${field}:>${printIso8601(value)}`;
+    case Operator.GTE:
+      return `${matchOp}${field}:>=${printIso8601(value)}`;
+    case Operator.LT:
+      return `${matchOp}${field}:<${printIso8601(value)}`;
+    case Operator.LTE:
+      return `${matchOp}${field}:<=${printIso8601(value)}`;
+    default:
+      throw new Error(`unknown operator [${operator}]`);
+  }
+};
+
+const emitFieldDateValueClause = (field, value, operator, match) => {
+  const matchOp = emitMatch(match);
+  const { granularity, resolve } = value;
+  const date = resolve();
+  if (granularity) {
+    switch (operator) {
+      case Operator.EQ:
+        const gte = granularity.iso8601(granularity.start(date));
+        const lt = granularity.iso8601(granularity.startOfNext(date));
+        return `${matchOp}${field}:(>=${gte} AND <${lt})`;
+      case Operator.GT:
+        return `${matchOp}${field}:>=${granularity.iso8601(granularity.startOfNext(date))}`;
+      case Operator.GTE:
+        return `${matchOp}${field}:>=${granularity.iso8601(granularity.start(date))}`;
+      case Operator.LT:
+        return `${matchOp}${field}:<${granularity.iso8601(granularity.start(date))}`;
+      case Operator.LTE:
+        return `${matchOp}${field}:<${granularity.iso8601(granularity.startOfNext(date))}`;
+      default:
+        throw new Error(`unknown operator [${operator}]`);
+    }
+  }
+  return emitFieldDateLikeClause(field, date, operator, match);
+};
+
+const emitFieldNumericClause = (field, value, operator, match) => {
+  const matchOp = emitMatch(match);
+  switch (operator) {
+    case Operator.EQ:
+      return `${matchOp}${field}:${value}`;
+    case Operator.GT:
+      return `${matchOp}${field}:>${value}`;
+    case Operator.GTE:
+      return `${matchOp}${field}:>=${value}`;
+    case Operator.LT:
+      return `${matchOp}${field}:<${value}`;
+    case Operator.LTE:
+      return `${matchOp}${field}:<=${value}`;
+    default:
+      throw new Error(`unknown operator [${operator}]`);
+  }
+};
+
+const emitFieldStringClause = (field, value, match) => {
+  const matchOp = emitMatch(match);
+  if (value.match(/\s/)) {
+    return `${matchOp}${field}:"${value}"`;
+  }
+  return `${matchOp}${field}:${value}`;
+};
+
+const emitFieldBooleanClause = (field, value, match) => {
+  const matchOp = emitMatch(match);
+  return `${matchOp}${field}:${value}`;
+};
+
+const emitFieldSingleValueClause = (field, value, operator, match) => {
+  if (isDateValue(value)) {
+    return emitFieldDateValueClause(field, value, operator, match);
+  }
+  if (isDateLike(value)) {
+    return emitFieldDateLikeClause(field, value, operator, match);
+  }
+  if (isString(value)) {
+    return emitFieldStringClause(field, value, match);
+  }
+  if (isNumber(value)) {
+    return emitFieldNumericClause(field, value, operator, match);
+  }
+  if (isBoolean(value)) {
+    return emitFieldBooleanClause(field, value, match);
+  }
+  throw new Error(`unknown type of field value [${value}]`);
+};
+
+const emitFieldClause = (clause) => {
+  const { field, value, operator, match } = clause;
+  if (!isArray(value)) {
+    return emitFieldSingleValueClause(field, value, operator, match);
+  }
+  const matchOp = emitMatch(match);
+  const clauses = value.map(v => emitFieldSingleValueClause(field, v, operator)).join(' OR ');
+  return `${matchOp}(${clauses})`;
+};
+
+const emitTermClause = (clause) => {
+  const { value, match } = clause;
+  const matchOp = emitMatch(match);
+  return `${matchOp}${value}`;
+};
+
+const emitIsClause = (clause) => {
+  const { flag, match } = clause;
+  return AST.Match.isMust(match) ? `+${flag}:true` : `+${flag}:false`;
+};
+
+export const astToEsQueryString = (ast) => {
+
+  if (ast.clauses.length === 0) {
+    return '';
+  }
+
+  return ast.clauses.map(clause => {
+    if (AST.Field.isInstance(clause)) {
+      return emitFieldClause(clause)
+    }
+    if (AST.Term.isInstance(clause)) {
+      return emitTermClause(clause);
+    }
+    if (AST.Is.isInstance(clause)) {
+      return emitIsClause(clause);
+    }
+    throw new Error(`unknown clause type [${JSON.stringify(clause)}]`);
+  }).join(' ');
+};
diff --git a/src/components/search_bar/query/ast_to_es_query_string.test.js b/src/components/search_bar/query/ast_to_es_query_string.test.js
new file mode 100644
index 00000000000..a813a6de145
--- /dev/null
+++ b/src/components/search_bar/query/ast_to_es_query_string.test.js
@@ -0,0 +1,112 @@
+import { AST } from './ast';
+import moment from 'moment/moment';
+import { dateValue } from './date_value';
+import { Granularity } from './date_format';
+import { astToEsQueryString } from './ast_to_es_query_string';
+
+describe('astToEsQueryString', () => {
+
+  test(`ast - ''`, () => {
+    const query = astToEsQueryString(AST.create([]));
+    expect(query).toMatchSnapshot();
+  });
+
+  test(`ast - 'john -sales'`, () => {
+    const query = astToEsQueryString(AST.create([
+      AST.Term.must('john'),
+      AST.Term.mustNot('sales'),
+    ]));
+    expect(query).toMatchSnapshot();
+  });
+
+  test(`ast - '-group:es group:kibana -group:beats group:logstash'`, () => {
+    const query = astToEsQueryString(AST.create([
+      AST.Field.mustNot.eq('group', 'es'),
+      AST.Field.must.eq('group', 'kibana'),
+      AST.Field.mustNot.eq('group', 'beats'),
+      AST.Field.must.eq('group', 'logstash')
+    ]));
+    expect(query).toMatchSnapshot();
+  });
+
+  test(`ast - 'is:online group:kibana john'`, () => {
+    const query = astToEsQueryString(AST.create([
+      AST.Is.must('online'),
+      AST.Field.must.eq('group', 'kibana'),
+      AST.Term.must('john')
+    ]));
+    expect(query).toMatchSnapshot();
+  });
+
+  test(`ast - 'john -doe is:online group:eng group:es -group:kibana -is:active'`, () => {
+    const query = astToEsQueryString(AST.create([
+      AST.Term.must('john'),
+      AST.Term.mustNot('doe'),
+      AST.Is.must('online'),
+      AST.Field.must.eq('group', 'eng'),
+      AST.Field.must.eq('group', 'es'),
+      AST.Field.mustNot.eq('group', 'kibana'),
+      AST.Is.mustNot('active')
+    ]));
+    expect(query).toMatchSnapshot();
+  });
+
+  test(`ast - 'john group:(eng or es) -group:kibana'`, () => {
+    const query = astToEsQueryString(AST.create([
+      AST.Term.must('john'),
+      AST.Field.must.eq('group', ['eng', 'es']),
+      AST.Field.mustNot.eq('group', 'kibana')
+    ]));
+    expect(query).toMatchSnapshot();
+  });
+
+  test(`ast - 'john group:(eng or "marketing org") -group:"kibana team"`, () => {
+    const query = astToEsQueryString(AST.create([
+      AST.Term.must('john'),
+      AST.Field.must.eq('group', ['eng', 'marketing org']),
+      AST.Field.mustNot.eq('group', 'kibana team')
+    ]));
+    expect(query).toMatchSnapshot();
+  });
+
+  test(`ast - count>3`, () => {
+    const query = astToEsQueryString(AST.create([
+      AST.Field.must.gt('count', 3)
+    ]));
+    expect(query).toMatchSnapshot();
+  });
+
+  test(`ast - -count<=4 size<5 age>=3 -number>9`, () => {
+    const query = astToEsQueryString(AST.create([
+      AST.Field.mustNot.lte('count', 4),
+      AST.Field.must.lt('size', 5),
+      AST.Field.must.gte('age', 3),
+      AST.Field.mustNot.gt('number', 9),
+    ]));
+    expect(query).toMatchSnapshot();
+  });
+
+  test(`ast - date>='2004-03-22'`, () => {
+    const query = astToEsQueryString(AST.create([
+      AST.Field.must.gte('date', dateValue(moment.utc('2004-03-22'), Granularity.DAY))
+    ]));
+    expect(query).toMatchSnapshot();
+  });
+
+  test(`ast - date:'2004-03' -date<'2004-03-10'`, () => {
+    const query = astToEsQueryString(AST.create([
+      AST.Field.must.eq('date', dateValue(moment.utc('2004-03'), Granularity.MONTH)),
+      AST.Field.mustNot.lt('date', dateValue(moment.utc('2004-03-10'), Granularity.DAY))
+    ]));
+    expect(query).toMatchSnapshot();
+  });
+
+  test(`ast - date>'2004-02' -otherDate>='2004-03-10'`, () => {
+    const query = astToEsQueryString(AST.create([
+      AST.Field.must.gt('date', dateValue(moment.utc('2004-02'), Granularity.MONTH)),
+      AST.Field.mustNot.gte('date', dateValue(moment.utc('2004-03-10'), Granularity.DAY))
+    ]));
+    expect(query).toMatchSnapshot();
+  });
+
+});
diff --git a/src/components/search_bar/query/date_format.js b/src/components/search_bar/query/date_format.js
index c013b9d2157..e333b1f3138 100644
--- a/src/components/search_bar/query/date_format.js
+++ b/src/components/search_bar/query/date_format.js
@@ -12,28 +12,32 @@ export const Granularity = Object.freeze({
     js: 'day',
     isSame: (d1, d2) => d1.isSame(d2, 'day'),
     start: (date) => date.startOf('day'),
-    startOfNext: (date) => date.add(1, 'days').startOf('day')
+    startOfNext: (date) => date.add(1, 'days').startOf('day'),
+    iso8601: (date) => date.format('YYYY-MM-DD')
   },
   WEEK: {
     es: 'w',
     js: 'week',
     isSame: (d1, d2) => d1.isSame(d2, 'week'),
     start: (date) => date.startOf('week'),
-    startOfNext: (date) => date.add(1, 'weeks').startOf('week')
+    startOfNext: (date) => date.add(1, 'weeks').startOf('week'),
+    iso8601: (date) => date.format('YYYY-MM-DD')
   },
   MONTH: {
     es: 'M',
     js: 'month',
     isSame: (d1, d2) => d1.isSame(d2, 'month'),
     start: (date) => date.startOf('month'),
-    startOfNext: (date) => date.add(1, 'months').startOf('month')
+    startOfNext: (date) => date.add(1, 'months').startOf('month'),
+    iso8601: (date) => date.format('YYYY-MM')
   },
   YEAR: {
     es: 'y',
     js: 'year',
     isSame: (d1, d2) => d1.isSame(d2, 'year'),
     start: (date) => date.startOf('year'),
-    startOfNext: (date) => date.add(1, 'years').startOf('year')
+    startOfNext: (date) => date.add(1, 'years').startOf('year'),
+    iso8601: (date) => date.format('YYYY')
   }
 });
 
diff --git a/src/components/search_bar/query/query.js b/src/components/search_bar/query/query.js
index d83357ef030..2ce2bd2d299 100644
--- a/src/components/search_bar/query/query.js
+++ b/src/components/search_bar/query/query.js
@@ -1,7 +1,8 @@
 import { defaultSyntax } from './default_syntax';
 import { executeAst } from './execute_ast';
 import { isNil, isString } from '../../../services/predicate';
-import { astToEs } from './ast_to_es';
+import { astToEsQueryDsl } from './ast_to_es_query_dsl';
+import { astToEsQueryString } from './ast_to_es_query_string';
 import { dateValueParser } from './date_value';
 import { AST } from './ast';
 
@@ -168,7 +169,12 @@ export class Query {
    */
   static toESQuery(query, options = {}) {
     const q = isString(query) ? Query.parse(query) : query;
-    return astToEs(q.ast, options);
+    return astToEsQueryDsl(q.ast, options);
+  }
+
+  static toESQueryString(query, options = {}) {
+    const q = isString(query) ? Query.parse(query) : query;
+    return astToEsQueryString(q.ast, options);
   }
 
 }