This is an automated email from the ASF dual-hosted git repository.

fjy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-druid.git


The following commit(s) were added to refs/heads/master by this push:
     new cc4450d  Web console: add reindex (ingestSegment firehose) to the data 
loader (#8181)
cc4450d is described below

commit cc4450db12a4042f00e23663371298276769c134
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Mon Jul 29 14:41:27 2019 -0700

    Web console: add reindex (ingestSegment firehose) to the data loader (#8181)
    
    * tidy up nulls
    
    * standardize more on undefined
    
    * updated licenses
    
    * do not do heavy handed rendering
    
    * reindex from druid
    
    * tidy up
    
    * add inline firehose
    
    * add husky
    
    * sass lint
    
    * better suggestion
    
    * fix script typo
    
    * adjust time formats
    
    * add missing time formats
    
    * use term 'reindex'
    
    * fix lodash.compact
---
 licenses.yaml                                      |  10 --
 licenses/bin/lodash.compact.MIT                    |  23 ---
 .../assets/{druid.png => ingestsegment.png}        | Bin
 web-console/assets/inline.png                      | Bin 0 -> 4744 bytes
 web-console/package-lock.json                      | 153 ++++++++++++++++--
 web-console/package.json                           |  18 ++-
 web-console/sasslint.json                          |   5 +
 .../src/components/array-input/array-input.tsx     |   3 +-
 web-console/src/components/auto-form/auto-form.tsx |   1 +
 .../src/components/json-input/json-input.tsx       |   4 +-
 web-console/src/utils/druid-query.ts               |   7 +-
 web-console/src/utils/druid-time.ts                |  55 ++++---
 web-console/src/utils/general.tsx                  |   8 +
 web-console/src/utils/ingestion-spec.tsx           | 172 +++++++++++++++++++--
 .../src/views/load-data-view/load-data-view.scss   |   1 +
 .../src/views/load-data-view/load-data-view.tsx    |  52 +++++--
 16 files changed, 410 insertions(+), 102 deletions(-)

diff --git a/licenses.yaml b/licenses.yaml
index 3e8907b..15458fc 100644
--- a/licenses.yaml
+++ b/licenses.yaml
@@ -2626,16 +2626,6 @@ license_file_path: licenses/bin/js-tokens.MIT
 
 ---
 
-name: "lodash.compact"
-license_category: binary
-module: web-console
-license_name: MIT License
-copyright: John-David Dalton
-version: 3.0.1
-license_file_path: licenses/bin/lodash.compact.MIT
-
----
-
 name: "lodash.debounce"
 license_category: binary
 module: web-console
diff --git a/licenses/bin/lodash.compact.MIT b/licenses/bin/lodash.compact.MIT
deleted file mode 100644
index bcbe13d..0000000
--- a/licenses/bin/lodash.compact.MIT
+++ /dev/null
@@ -1,23 +0,0 @@
-The MIT License (MIT)
-
-Copyright 2012-2016 The Dojo Foundation <http://dojofoundation.org/>
-Based on Underscore.js, copyright 2009-2016 Jeremy Ashkenas,
-DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/web-console/assets/druid.png b/web-console/assets/ingestsegment.png
similarity index 100%
rename from web-console/assets/druid.png
rename to web-console/assets/ingestsegment.png
diff --git a/web-console/assets/inline.png b/web-console/assets/inline.png
new file mode 100644
index 0000000..030388d
Binary files /dev/null and b/web-console/assets/inline.png differ
diff --git a/web-console/package-lock.json b/web-console/package-lock.json
index c9c5c60..57294dc 100644
--- a/web-console/package-lock.json
+++ b/web-console/package-lock.json
@@ -1282,15 +1282,6 @@
       "integrity": 
"sha512-0GJhzBdvsW2RUccNHOBkabI8HZVdOXmXbXhuKlDEd5Vv12P7oAVGfomGp3Ne21o5D/qu1WmthlNKFaoZJJeErA==",
       "dev": true
     },
-    "@types/lodash.compact": {
-      "version": "3.0.6",
-      "resolved": 
"https://registry.npmjs.org/@types/lodash.compact/-/lodash.compact-3.0.6.tgz";,
-      "integrity": 
"sha512-0pDKTX4alTyxH85Y5Al4YzS8oriqBQykADW6zLAHkZwNBMPXFIhdE2ctg0Z2GVcZsABxo5CI/J3vmHrFkdQBfA==",
-      "dev": true,
-      "requires": {
-        "@types/lodash": "*"
-      }
-    },
     "@types/lodash.debounce": {
       "version": "4.0.6",
       "resolved": 
"https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.6.tgz";,
@@ -1327,6 +1318,12 @@
       "integrity": 
"sha512-gojym4tX0FWeV2gsW4Xmzo5wxGjXGm550oVUII7f7G5o4BV6c7DBdiG1RRQd+y1bvqRyYtPfMK85UM95vsapqQ==",
       "dev": true
     },
+    "@types/normalize-package-data": {
+      "version": "2.4.0",
+      "resolved": 
"https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz";,
+      "integrity": 
"sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==",
+      "dev": true
+    },
     "@types/numeral": {
       "version": "0.0.25",
       "resolved": 
"https://registry.npmjs.org/@types/numeral/-/numeral-0.0.25.tgz";,
@@ -6091,6 +6088,106 @@
       "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
       "dev": true
     },
+    "husky": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/husky/-/husky-3.0.1.tgz";,
+      "integrity": 
"sha512-PXBv+iGKw23GHUlgELRlVX9932feFL407/wHFwtsGeArp0dDM4u+/QusSQwPKxmNgjpSL+ustbOdQ2jetgAZbA==",
+      "dev": true,
+      "requires": {
+        "chalk": "^2.4.2",
+        "cosmiconfig": "^5.2.1",
+        "execa": "^1.0.0",
+        "get-stdin": "^7.0.0",
+        "is-ci": "^2.0.0",
+        "opencollective-postinstall": "^2.0.2",
+        "pkg-dir": "^4.2.0",
+        "please-upgrade-node": "^3.1.1",
+        "read-pkg": "^5.1.1",
+        "run-node": "^1.0.0",
+        "slash": "^3.0.0"
+      },
+      "dependencies": {
+        "find-up": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz";,
+          "integrity": 
"sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+          "dev": true,
+          "requires": {
+            "locate-path": "^5.0.0",
+            "path-exists": "^4.0.0"
+          }
+        },
+        "get-stdin": {
+          "version": "7.0.0",
+          "resolved": 
"https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz";,
+          "integrity": 
"sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==",
+          "dev": true
+        },
+        "locate-path": {
+          "version": "5.0.0",
+          "resolved": 
"https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz";,
+          "integrity": 
"sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+          "dev": true,
+          "requires": {
+            "p-locate": "^4.1.0"
+          }
+        },
+        "p-locate": {
+          "version": "4.1.0",
+          "resolved": 
"https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz";,
+          "integrity": 
"sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+          "dev": true,
+          "requires": {
+            "p-limit": "^2.2.0"
+          }
+        },
+        "parse-json": {
+          "version": "5.0.0",
+          "resolved": 
"https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz";,
+          "integrity": 
"sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==",
+          "dev": true,
+          "requires": {
+            "@babel/code-frame": "^7.0.0",
+            "error-ex": "^1.3.1",
+            "json-parse-better-errors": "^1.0.1",
+            "lines-and-columns": "^1.1.6"
+          }
+        },
+        "path-exists": {
+          "version": "4.0.0",
+          "resolved": 
"https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz";,
+          "integrity": 
"sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+          "dev": true
+        },
+        "pkg-dir": {
+          "version": "4.2.0",
+          "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz";,
+          "integrity": 
"sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+          "dev": true,
+          "requires": {
+            "find-up": "^4.0.0"
+          }
+        },
+        "read-pkg": {
+          "version": "5.2.0",
+          "resolved": 
"https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz";,
+          "integrity": 
"sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
+          "dev": true,
+          "requires": {
+            "@types/normalize-package-data": "^2.4.0",
+            "normalize-package-data": "^2.5.0",
+            "parse-json": "^5.0.0",
+            "type-fest": "^0.6.0"
+          }
+        },
+        "slash": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz";,
+          "integrity": 
"sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+          "dev": true
+        }
+      }
+    },
     "iconv-lite": {
       "version": "0.4.24",
       "resolved": 
"https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz";,
@@ -7487,11 +7584,6 @@
       "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=",
       "dev": true
     },
-    "lodash.compact": {
-      "version": "3.0.1",
-      "resolved": 
"https://registry.npmjs.org/lodash.compact/-/lodash.compact-3.0.1.tgz";,
-      "integrity": "sha1-VAzjg3dFl1gHRx4WtKK6IeclbKU="
-    },
     "lodash.debounce": {
       "version": "4.0.8",
       "resolved": 
"https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz";,
@@ -8730,6 +8822,12 @@
         "wrappy": "1"
       }
     },
+    "opencollective-postinstall": {
+      "version": "2.0.2",
+      "resolved": 
"https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz";,
+      "integrity": 
"sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==",
+      "dev": true
+    },
     "opener": {
       "version": "1.5.1",
       "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz";,
@@ -9150,6 +9248,15 @@
         "find-up": "^3.0.0"
       }
     },
+    "please-upgrade-node": {
+      "version": "3.1.1",
+      "resolved": 
"https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.1.1.tgz";,
+      "integrity": 
"sha512-KY1uHnQ2NlQHqIJQpnh/i54rKkuxCEBx+voJIS/Mvb+L2iYd2NMotwduhKTMjfC1uKoX3VXOxLjIYG66dfJTVQ==",
+      "dev": true,
+      "requires": {
+        "semver-compare": "^1.0.0"
+      }
+    },
     "pn": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz";,
@@ -10788,6 +10895,12 @@
       "integrity": 
"sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==",
       "dev": true
     },
+    "run-node": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz";,
+      "integrity": 
"sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A==",
+      "dev": true
+    },
     "run-queue": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz";,
@@ -11150,6 +11263,12 @@
       "integrity": 
"sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
       "dev": true
     },
+    "semver-compare": {
+      "version": "1.0.0",
+      "resolved": 
"https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz";,
+      "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=",
+      "dev": true
+    },
     "send": {
       "version": "0.17.1",
       "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz";,
@@ -13089,6 +13208,12 @@
         "prelude-ls": "~1.1.2"
       }
     },
+    "type-fest": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz";,
+      "integrity": 
"sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
+      "dev": true
+    },
     "type-is": {
       "version": "1.6.18",
       "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz";,
diff --git a/web-console/package.json b/web-console/package.json
index 8d7fbd9..248f7d7 100644
--- a/web-console/package.json
+++ b/web-console/package.json
@@ -24,6 +24,12 @@
       "<rootDir>src/setup-tests.ts"
     ]
   },
+  "husky": {
+    "hooks": {
+      "pre-commit": "npm run tslint-changed-only && npm run 
sasslint-changed-only",
+      "pre-push": "npm run tslint && npm run sasslint"
+    }
+  },
   "prettier": {
     "trailingComma": "all",
     "tabWidth": 2,
@@ -35,17 +41,20 @@
   "scripts": {
     "compile": "./script/build",
     "pretest": "./script/build",
-    "test": "npm run tslint && npm run stylelint && jest --silent 2>&1",
+    "test": "npm run tslint && npm run sasslint && jest --silent 2>&1",
     "coverage": "jest --coverage",
     "update-snapshots": "jest -u",
     "tslint": "./node_modules/.bin/tslint -c tslint.json --project 
tsconfig.json --formatters-dir ./node_modules/awesome-code-style/formatter 
'src/**/*.ts?(x)'",
     "tslint-fix": "npm run tslint -- --fix",
     "tslint-changed-only": "git diff --diff-filter=ACMR --cached --name-only | 
grep -E \\.tsx\\?$ | xargs ./node_modules/.bin/tslint -c tslint.json --project 
tsconfig.json --formatters-dir ./node_modules/awesome-code-style/formatter",
     "tslint-fix-changed-only": "npm run tslint-changed-only -- --fix",
+    "sasslint": "./node_modules/.bin/stylelint --config sasslint.json 
'src/**/*.scss'",
+    "sasslint-fix": "npm run sasslint -- --fix",
+    "sasslint-changed-only": "git diff --diff-filter=ACMR --name-only | grep 
-E \\.scss$ | xargs ./node_modules/.bin/stylelint --config sasslint.json",
+    "sasslint-fix-changed-only": "npm run sasslint-changed-only -- --fix",
     "generate-licenses-file": "license-checker --production --json --out 
licenses.json",
     "check-licenses": "license-checker --production --onlyAllow 
'Apache-1.1;Apache-2.0;BSD-2-Clause;BSD-3-Clause;MIT;CC0-1.0' --summary",
-    "start": "webpack-dev-server --hot --open",
-    "stylelint": "stylelint 'src/**/*.scss'"
+    "start": "webpack-dev-server --hot --open"
   },
   "dependencies": {
     "@blueprintjs/core": "^3.17.1",
@@ -60,7 +69,6 @@
     "file-saver": "^2.0.2",
     "has-own-prop": "^2.0.0",
     "hjson": "^3.1.2",
-    "lodash.compact": "^3.0.1",
     "lodash.debounce": "^4.0.8",
     "lodash.escape": "^4.0.1",
     "memoize-one": "^5.0.5",
@@ -86,7 +94,6 @@
     "@types/file-saver": "^2.0.1",
     "@types/hjson": "^2.4.1",
     "@types/jest": "^24.0.15",
-    "@types/lodash.compact": "^3.0.6",
     "@types/lodash.debounce": "^4.0.6",
     "@types/lodash.escape": "^4.0.6",
     "@types/memoize-one": "^4.1.1",
@@ -105,6 +112,7 @@
     "enzyme-adapter-react-16": "^1.14.0",
     "enzyme-to-json": "^3.3.5",
     "fs-extra": "^8.1.0",
+    "husky": "^3.0.1",
     "identity-obj-proxy": "^3.0.0",
     "ignore-styles": "^5.0.1",
     "jest": "^24.8.0",
diff --git a/web-console/sasslint.json b/web-console/sasslint.json
new file mode 100644
index 0000000..f4880c9
--- /dev/null
+++ b/web-console/sasslint.json
@@ -0,0 +1,5 @@
+{
+  "extends": "awesome-code-style/sasslint.json",
+  "rules": {
+  }
+}
diff --git a/web-console/src/components/array-input/array-input.tsx 
b/web-console/src/components/array-input/array-input.tsx
index 9d7a451..5411f6f 100644
--- a/web-console/src/components/array-input/array-input.tsx
+++ b/web-console/src/components/array-input/array-input.tsx
@@ -17,9 +17,10 @@
  */
 
 import { TextArea } from '@blueprintjs/core';
-import compact from 'lodash.compact';
 import React from 'react';
 
+import { compact } from '../../utils';
+
 export interface ArrayInputProps {
   className?: string;
   values: string[];
diff --git a/web-console/src/components/auto-form/auto-form.tsx 
b/web-console/src/components/auto-form/auto-form.tsx
index ab2f9c8..44b9e0c 100644
--- a/web-console/src/components/auto-form/auto-form.tsx
+++ b/web-console/src/components/auto-form/auto-form.tsx
@@ -205,6 +205,7 @@ export class AutoForm<T extends Record<string, any>> 
extends React.PureComponent
         value={deepGet(model as any, field.name)}
         onChange={(v: any) => this.fieldChange(field, v)}
         updateInputValidity={updateInputValidity}
+        placeholder={field.placeholder}
       />
     );
   }
diff --git a/web-console/src/components/json-input/json-input.tsx 
b/web-console/src/components/json-input/json-input.tsx
index 6ecea6b..c8d13a3 100644
--- a/web-console/src/components/json-input/json-input.tsx
+++ b/web-console/src/components/json-input/json-input.tsx
@@ -25,6 +25,7 @@ interface JSONInputProps {
   onChange: (newJSONValue: any) => void;
   value: any;
   updateInputValidity?: (valueValid: boolean) => void;
+  placeholder?: string;
   focus?: boolean;
   width?: string;
   height?: string;
@@ -59,7 +60,7 @@ export class JSONInput extends 
React.PureComponent<JSONInputProps, JSONInputStat
   }
 
   render(): JSX.Element {
-    const { onChange, updateInputValidity, focus, width, height } = this.props;
+    const { onChange, updateInputValidity, placeholder, focus, width, height } 
= this.props;
     const { stringValue } = this.state;
     return (
       <AceEditor
@@ -79,6 +80,7 @@ export class JSONInput extends 
React.PureComponent<JSONInputProps, JSONInputStat
         showPrintMargin={false}
         showGutter={false}
         value={stringValue}
+        placeholder={placeholder}
         editorProps={{
           $blockScrolling: Infinity,
         }}
diff --git a/web-console/src/utils/druid-query.ts 
b/web-console/src/utils/druid-query.ts
index db22700..267bd38 100644
--- a/web-console/src/utils/druid-query.ts
+++ b/web-console/src/utils/druid-query.ts
@@ -18,7 +18,8 @@
 
 import axios from 'axios';
 import { AxiosResponse } from 'axios';
-import compact from 'lodash.compact';
+
+import { assemble } from './general';
 
 export function parseHtmlError(htmlStr: string): string | undefined {
   const startIndex = htmlStr.indexOf('</h3><pre>');
@@ -37,12 +38,12 @@ export function getDruidErrorMessage(e: any) {
   switch (typeof data) {
     case 'object':
       return (
-        compact([
+        assemble(
           data.error,
           data.errorMessage,
           data.errorClass,
           data.host ? `on host ${data.host}` : undefined,
-        ]).join(' / ') || e.message
+        ).join(' / ') || e.message
       );
 
     case 'string':
diff --git a/web-console/src/utils/druid-time.ts 
b/web-console/src/utils/druid-time.ts
index 08cba49..febe2c1 100644
--- a/web-console/src/utils/druid-time.ts
+++ b/web-console/src/utils/druid-time.ts
@@ -18,9 +18,9 @@
 
 import { jodaFormatToRegExp } from './joda-to-regexp';
 
-export const BASIC_FORMAT_VALUES: string[] = ['iso', 'millis', 'posix'];
+export const BASIC_TIME_FORMATS: string[] = ['iso', 'posix', 'millis', 
'micro', 'nano'];
 
-export const DATE_FORMAT_VALUES: string[] = [
+export const DATE_ONLY_TIME_FORMATS: string[] = [
   'dd/MM/yyyy',
   'MM/dd/yyyy',
   'd/M/yy',
@@ -29,7 +29,7 @@ export const DATE_FORMAT_VALUES: string[] = [
   'M/d/yyyy',
 ];
 
-export const DATE_TIME_FORMAT_VALUES: string[] = [
+export const DATETIME_TIME_FORMATS: string[] = [
   'd/M/yyyy H:mm:ss',
   'M/d/yyyy H:mm:ss',
   'MM/dd/yyyy hh:mm:ss a',
@@ -37,38 +37,45 @@ export const DATE_TIME_FORMAT_VALUES: string[] = [
   'yyyy-MM-dd HH:mm:ss.S',
 ];
 
-const ALL_FORMAT_VALUES: string[] = BASIC_FORMAT_VALUES.concat(
-  DATE_FORMAT_VALUES,
-  DATE_TIME_FORMAT_VALUES,
+export const OTHER_TIME_FORMATS: string[] = ['MMM dd HH:mm:ss'];
+
+const ALL_FORMAT_VALUES: string[] = BASIC_TIME_FORMATS.concat(
+  DATE_ONLY_TIME_FORMATS,
+  DATETIME_TIME_FORMATS,
+  OTHER_TIME_FORMATS,
 );
 
-const EXAMPLE_DATE_ISO = '2015-10-29T23:00:00.000Z';
-const EXAMPLE_DATE_VALUE = Date.parse(EXAMPLE_DATE_ISO);
-const MIN_MILLIS = 3.15576e11; // 3 years in millis, so Tue Jan 01 1980
-const MAX_MILLIS = EXAMPLE_DATE_VALUE * 10;
-const MIN_POSIX = MIN_MILLIS / 1000;
-const MAX_POSIX = MAX_MILLIS / 1000;
+const MIN_POSIX = 3.15576e8; // 3 years in posix, so Tue Jan 01 1980
+const MIN_MILLIS = MIN_POSIX * 1000;
+const MIN_MICRO = MIN_MILLIS * 1000;
+const MIN_NANO = MIN_MICRO * 1000;
+const MAX_NANO = MIN_NANO * 1000;
 
 // copied from http://goo.gl/0ejHHW with small tweak to make dddd not pass on 
its own
 // tslint:disable-next-line:max-line-length
 export const ISO_MATCHER = 
/^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))(T((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)$/;
 
 export function timeFormatMatches(format: string, value: string | number): 
boolean {
-  if (format === 'iso') {
-    return ISO_MATCHER.test(String(value));
-  }
+  const absValue = Math.abs(Number(value));
+  switch (format) {
+    case 'iso':
+      return ISO_MATCHER.test(String(value));
 
-  if (format === 'millis') {
-    const absValue = Math.abs(Number(value));
-    return MIN_MILLIS < absValue && absValue < MAX_MILLIS;
-  }
+    case 'posix':
+      return MIN_POSIX < absValue && absValue < MIN_MILLIS;
 
-  if (format === 'posix') {
-    const absValue = Math.abs(Number(value));
-    return MIN_POSIX < absValue && absValue < MAX_POSIX;
-  }
+    case 'millis':
+      return MIN_MILLIS < absValue && absValue < MIN_MICRO;
 
-  return jodaFormatToRegExp(format).test(String(value));
+    case 'micro':
+      return MIN_MICRO < absValue && absValue < MIN_NANO;
+
+    case 'nano':
+      return MIN_NANO < absValue && absValue < MAX_NANO;
+
+    default:
+      return jodaFormatToRegExp(format).test(String(value));
+  }
 }
 
 export function possibleDruidFormatForValues(values: any[]): string | null {
diff --git a/web-console/src/utils/general.tsx 
b/web-console/src/utils/general.tsx
index b504420..33e1437 100644
--- a/web-console/src/utils/general.tsx
+++ b/web-console/src/utils/general.tsx
@@ -277,6 +277,14 @@ export function filterMap<T, Q>(xs: T[], f: (x: T, i: 
number) => Q | undefined):
   return xs.map(f).filter((x: Q | undefined) => typeof x !== 'undefined') as 
Q[];
 }
 
+export function compact<T>(xs: (T | undefined | false | null | '')[]): T[] {
+  return xs.filter(Boolean) as T[];
+}
+
+export function assemble<T>(...xs: (T | undefined | false | null | '')[]): T[] 
{
+  return xs.filter(Boolean) as T[];
+}
+
 export function alphanumericCompare(a: string, b: string): number {
   return String(a).localeCompare(b, undefined, { numeric: true });
 }
diff --git a/web-console/src/utils/ingestion-spec.tsx 
b/web-console/src/utils/ingestion-spec.tsx
index 6caf518..546f798 100644
--- a/web-console/src/utils/ingestion-spec.tsx
+++ b/web-console/src/utils/ingestion-spec.tsx
@@ -22,13 +22,20 @@ import React from 'react';
 import { Field } from '../components/auto-form/auto-form';
 import { ExternalLink } from '../components/external-link/external-link';
 
-import { BASIC_FORMAT_VALUES, DATE_FORMAT_VALUES, DATE_TIME_FORMAT_VALUES } 
from './druid-time';
+import {
+  BASIC_TIME_FORMATS,
+  DATE_ONLY_TIME_FORMATS,
+  DATETIME_TIME_FORMATS,
+  OTHER_TIME_FORMATS,
+} from './druid-time';
 import { deepGet, deepSet } from './object-change';
 
 // These constants are used to make sure that they are not constantly 
recreated thrashing the pure components
 export const EMPTY_OBJECT: any = {};
 export const EMPTY_ARRAY: any[] = [];
 
+const CURRENT_YEAR = new Date().getUTCFullYear();
+
 export interface IngestionSpec {
   type?: IngestionType;
   dataSchema: DataSchema;
@@ -48,6 +55,8 @@ export type IngestionComboType =
   | 'kinesis'
   | 'index:http'
   | 'index:local'
+  | 'index:ingestSegment'
+  | 'index:inline'
   | 'index:static-s3'
   | 'index:static-google-blobstore';
 
@@ -84,9 +93,11 @@ export function getIngestionComboType(spec: IngestionSpec): 
IngestionComboType |
       switch (firehose.type) {
         case 'local':
         case 'http':
+        case 'ingestSegment':
+        case 'inline':
         case 'static-s3':
         case 'static-google-blobstore':
-          return `index:${firehose.type}` as any;
+          return `index:${firehose.type}` as IngestionComboType;
       }
   }
 
@@ -101,6 +112,12 @@ export function getIngestionTitle(ingestionType: 
IngestionComboTypeWithExtra): s
     case 'index:http':
       return 'HTTP(s)';
 
+    case 'index:ingestSegment':
+      return 'Reindex from Druid';
+
+    case 'index:inline':
+      return 'Paste data';
+
     case 'index:static-s3':
       return 'Amazon S3';
 
@@ -129,7 +146,7 @@ export function getIngestionTitle(ingestionType: 
IngestionComboTypeWithExtra): s
 
 export function getIngestionImage(ingestionType: IngestionComboTypeWithExtra): 
string {
   const parts = ingestionType.split(':');
-  if (parts.length === 2) return parts[1];
+  if (parts.length === 2) return parts[1].toLowerCase();
   return ingestionType;
 }
 
@@ -369,14 +386,18 @@ const TIMESTAMP_SPEC_FORM_FIELDS: Field<TimestampSpec>[] 
= [
     defaultValue: 'auto',
     suggestions: [
       'auto',
-      ...BASIC_FORMAT_VALUES,
+      ...BASIC_TIME_FORMATS,
       {
         group: 'Date and time formats',
-        suggestions: DATE_TIME_FORMAT_VALUES,
+        suggestions: DATETIME_TIME_FORMATS,
       },
       {
         group: 'Date only formats',
-        suggestions: DATE_FORMAT_VALUES,
+        suggestions: DATE_ONLY_TIME_FORMATS,
+      },
+      {
+        group: 'Other time formats',
+        suggestions: OTHER_TIME_FORMATS,
       },
     ],
     isDefined: (timestampSpec: TimestampSpec) => 
isColumnTimestampSpec(timestampSpec),
@@ -710,11 +731,21 @@ export interface IoConfig {
 export interface Firehose {
   type: string;
   baseDir?: string;
-  filter?: string;
+  filter?: any;
   uris?: string[];
   prefixes?: string[];
   blobs?: { bucket: string; path: string }[];
   fetchTimeout?: number;
+
+  // ingestSegment
+  dataSource?: string;
+  interval?: string;
+  dimensions?: string[];
+  metrics?: string[];
+  maxInputSegmentBytesPerTask?: number;
+
+  // inline
+  data?: string;
 }
 
 export function getIoConfigFormFields(ingestionComboType: IngestionComboType): 
Field<IoConfig>[] {
@@ -794,6 +825,84 @@ export function getIoConfigFormFields(ingestionComboType: 
IngestionComboType): F
         },
       ];
 
+    case 'index:ingestSegment':
+      return [
+        firehoseType,
+        {
+          name: 'firehose.dataSource',
+          label: 'Datasource',
+          type: 'string',
+          info: <p>The datasource to fetch rows from.</p>,
+        },
+        {
+          name: 'firehose.interval',
+          label: 'Interval',
+          type: 'string',
+          placeholder: `${CURRENT_YEAR}-01-01/${CURRENT_YEAR + 1}-01-01`,
+          suggestions: [
+            `${CURRENT_YEAR}/${CURRENT_YEAR + 1}`,
+            `${CURRENT_YEAR}-01-01/${CURRENT_YEAR + 1}-01-01`,
+            `${CURRENT_YEAR}-01-01T00:00:00/${CURRENT_YEAR + 
1}-01-01T00:00:00`,
+          ],
+          info: (
+            <p>
+              A String representing ISO-8601 Interval. This defines the time 
range to fetch the data
+              over.
+            </p>
+          ),
+        },
+        {
+          name: 'firehose.dimensions',
+          label: 'Dimensions',
+          type: 'string-array',
+          placeholder: '(optional)',
+          info: (
+            <p>
+              The list of dimensions to select. If left empty, no dimensions 
are returned. If left
+              null or not defined, all dimensions are returned.
+            </p>
+          ),
+        },
+        {
+          name: 'firehose.metrics',
+          label: 'Metrics',
+          type: 'string-array',
+          placeholder: '(optional)',
+          info: (
+            <p>
+              The list of metrics to select. If left empty, no metrics are 
returned. If left null or
+              not defined, all metrics are selected.
+            </p>
+          ),
+        },
+        {
+          name: 'firehose.filter',
+          label: 'Filter',
+          type: 'json',
+          placeholder: '(optional)',
+          info: (
+            <p>
+              The{' '}
+              <ExternalLink 
href="https://druid.apache.org/docs/latest/querying/filters.html";>
+                filter
+              </ExternalLink>{' '}
+              to apply to the data as part of querying.
+            </p>
+          ),
+        },
+      ];
+
+    case 'index:inline':
+      return [
+        firehoseType,
+        {
+          name: 'firehose.data',
+          label: 'Data',
+          type: 'string',
+          info: <p>The data to ingest.</p>,
+        },
+      ];
+
     case 'index:static-s3':
       return [
         firehoseType,
@@ -962,8 +1071,8 @@ function issueWithFirehose(firehose: Firehose | 
undefined): string | undefined {
   if (!firehose.type) return 'missing a type';
   switch (firehose.type) {
     case 'local':
-      if (!firehose.baseDir) return "must have a 'baseDir'";
-      if (!firehose.filter) return "must have a 'filter'";
+      if (!firehose.baseDir) return `must have a 'baseDir'`;
+      if (!firehose.filter) return `must have a 'filter'`;
       break;
 
     case 'http':
@@ -972,6 +1081,15 @@ function issueWithFirehose(firehose: Firehose | 
undefined): string | undefined {
       }
       break;
 
+    case 'ingestSegment':
+      if (!firehose.dataSource) return `must have a 'dataSource'`;
+      if (!firehose.interval) return `must have an 'interval'`;
+      break;
+
+    case 'inline':
+      if (!firehose.data) return `must have 'data'`;
+      break;
+
     case 'static-s3':
       if (!nonEmptyArray(firehose.uris) && !nonEmptyArray(firehose.prefixes)) {
         return 'must have at least one uri or prefix';
@@ -1082,8 +1200,27 @@ export function getIoConfigTuningFormFields(
       ];
 
     case 'index:local':
+    case 'index:inline':
       return [];
 
+    case 'index:ingestSegment':
+      return [
+        {
+          name: 'firehose.maxFetchCapacityBytes',
+          label: 'Max fetch capacity bytes',
+          type: 'number',
+          defaultValue: 157286400,
+          info: (
+            <p>
+              When used with the native parallel index task, the maximum 
number of bytes of input
+              segments to process in a single task. If a single segment is 
larger than this number,
+              it will be processed by itself in a single task (input segments 
are never split across
+              tasks). Defaults to 150MB.
+            </p>
+          ),
+        },
+      ];
+
     case 'kafka':
     case 'kinesis':
       return [
@@ -1337,6 +1474,12 @@ export function guessDataSourceName(ioConfig: IoConfig): 
string | undefined {
 
         case 'http':
           return Array.isArray(firehose.uris) ? 
filenameFromPath(firehose.uris[0]) : undefined;
+
+        case 'ingestSegment':
+          return firehose.dataSource;
+
+        case 'inline':
+          return 'inline_data';
       }
 
       return;
@@ -1821,10 +1964,19 @@ export function updateIngestionType(
 }
 
 export function fillParser(spec: IngestionSpec, sampleData: string[]): 
IngestionSpec {
-  if (deepGet(spec, 'ioConfig.firehose.type') === 'sql') {
+  const firehoseType = deepGet(spec, 'ioConfig.firehose.type');
+
+  if (firehoseType === 'sql') {
     return deepSet(spec, 'dataSchema.parser', { type: 'map' });
   }
 
+  if (firehoseType === 'ingestSegment') {
+    return deepSet(spec, 'dataSchema.parser', {
+      type: 'string',
+      parseSpec: { format: 'timeAndDims' },
+    });
+  }
+
   const parseSpec = guessParseSpec(sampleData);
   if (!parseSpec) return spec;
 
diff --git a/web-console/src/views/load-data-view/load-data-view.scss 
b/web-console/src/views/load-data-view/load-data-view.scss
index 003b1dc..227ebfb 100644
--- a/web-console/src/views/load-data-view/load-data-view.scss
+++ b/web-console/src/views/load-data-view/load-data-view.scss
@@ -63,6 +63,7 @@
     .main {
       height: 100%;
       padding: 0;
+      overflow: auto;
 
       .bp3-card {
         position: relative;
diff --git a/web-console/src/views/load-data-view/load-data-view.tsx 
b/web-console/src/views/load-data-view/load-data-view.tsx
index f3de329..8a0bca7 100644
--- a/web-console/src/views/load-data-view/load-data-view.tsx
+++ b/web-console/src/views/load-data-view/load-data-view.tsx
@@ -120,6 +120,7 @@ import {
   getOverlordModules,
   HeaderAndRows,
   headerAndRowsFromSampleResponse,
+  SampleEntry,
   sampleForConnect,
   sampleForFilter,
   sampleForParser,
@@ -139,14 +140,19 @@ import { TransformTable } from 
'./transform-table/transform-table';
 
 import './load-data-view.scss';
 
-function showRawLine(line: string): string {
-  if (line.includes('\n')) {
-    return `<Multi-line row, length: ${line.length}>`;
+function showRawLine(line: SampleEntry): string {
+  const raw = line.raw;
+  if (raw.includes('\n')) {
+    return `[Multi-line row, length: ${raw.length}]`;
   }
-  if (line.length > 1000) {
-    return line.substr(0, 1000) + '...';
+  if (raw.length > 1000) {
+    return raw.substr(0, 1000) + '...';
   }
-  return line;
+  return raw;
+}
+
+function showBlankLine(line: SampleEntry): string {
+  return line.parsed ? `[Row: ${JSON.stringify(line.parsed)}]` : '[Binary 
data]';
 }
 
 function getTimestampSpec(headerAndRows: HeaderAndRows | null): TimestampSpec {
@@ -244,7 +250,7 @@ export interface LoadDataViewState {
   specialColumnsOnly: boolean;
 
   // for ioConfig
-  inputQueryState: QueryState<string[]>;
+  inputQueryState: QueryState<SampleEntry[]>;
 
   // for parser
   parserQueryState: QueryState<HeaderAndRows>;
@@ -551,8 +557,10 @@ export class LoadDataView extends 
React.PureComponent<LoadDataViewProps, LoadDat
           {this.renderIngestionCard('index:static-s3')}
           {this.renderIngestionCard('index:static-google-blobstore')}
           {this.renderIngestionCard('hadoop')}
+          {this.renderIngestionCard('index:ingestSegment')}
           {this.renderIngestionCard('index:http')}
           {this.renderIngestionCard('index:local')}
+          {this.renderIngestionCard('index:inline')}
           {/* this.renderIngestionCard('example') */}
           {this.renderIngestionCard('other')}
         </div>
@@ -611,6 +619,24 @@ export class LoadDataView extends 
React.PureComponent<LoadDataViewProps, LoadDat
           </>
         );
 
+      case 'index:ingestSegment':
+        return (
+          <>
+            <p>Reindex data from existing Druid segments.</p>
+            <p>
+              Reindexing data allows you to filter rows, add, transform, and 
delete columns, as well
+              as change the partitioning of the data.
+            </p>
+          </>
+        );
+
+      case 'index:inline':
+        return (
+          <>
+            <p>Ingest a small amount of data directly from the clipboard.</p>
+          </>
+        );
+
       case 'index:static-s3':
         return <p>Load text based data from Amazon S3.</p>;
 
@@ -672,6 +698,8 @@ export class LoadDataView extends 
React.PureComponent<LoadDataViewProps, LoadDat
     switch (selectedComboType) {
       case 'index:http':
       case 'index:local':
+      case 'index:ingestSegment':
+      case 'index:inline':
       case 'index:static-s3':
       case 'index:static-google-blobstore':
       case 'kafka':
@@ -812,7 +840,7 @@ export class LoadDataView extends 
React.PureComponent<LoadDataViewProps, LoadDat
 
     this.setState({
       cacheKey: sampleResponse.cacheKey,
-      inputQueryState: new QueryState({ data: sampleResponse.data.map((d: any) 
=> d.raw) }),
+      inputQueryState: new QueryState({ data: sampleResponse.data }),
     });
   }
 
@@ -841,8 +869,8 @@ export class LoadDataView extends 
React.PureComponent<LoadDataViewProps, LoadDat
           className="raw-lines"
           value={
             inputData.length
-              ? (inputData.every(l => !l)
-                  ? inputData.map(_ => '<Binary data>')
+              ? (inputData.every(l => !l.raw)
+                  ? inputData.map(showBlankLine)
                   : inputData.map(showRawLine)
                 ).join('\n')
               : 'No data returned from sampler'
@@ -910,7 +938,9 @@ export class LoadDataView extends 
React.PureComponent<LoadDataViewProps, LoadDat
           disabled: !inputQueryState.data,
           onNextStep: () => {
             if (!inputQueryState.data) return;
-            this.updateSpec(fillDataSourceName(fillParser(spec, 
inputQueryState.data)));
+            this.updateSpec(
+              fillDataSourceName(fillParser(spec, inputQueryState.data.map(l 
=> l.raw))),
+            );
           },
         })}
       </>


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to