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 b6b1e61  Added tslint to web console (#7280)
b6b1e61 is described below

commit b6b1e6160ca38e3140e151f7bf5133e6809ae1be
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Sun Mar 17 09:23:17 2019 -0700

    Added tslint to web console (#7280)
    
    * added tslint to web console
    
    * added react linting and made rules stricter
    
    * order imports
    
    * update package-lock
---
 web-console/package-lock.json                      | 117 +++++++++++++++++++
 web-console/package.json                           |   3 +
 web-console/script/create-sql-function-doc         |  41 +++++--
 .../a-shim-for-react-props.ts}                     |  11 +-
 .../react-table-defaults.tsx                       |   4 +-
 web-console/src/components/auto-form.tsx           |  19 ++--
 web-console/src/components/filler.tsx              |  25 ++---
 web-console/src/components/header-bar.tsx          |  52 ++++++---
 web-console/src/components/loader.tsx              |  31 +++--
 web-console/src/components/rule-editor.tsx         |  30 ++---
 web-console/src/components/sql-control.tsx         |  57 +++++-----
 web-console/src/console-application.tsx            |  82 +++++++++-----
 web-console/src/dialogs/about-dialog.tsx           |   7 +-
 web-console/src/dialogs/async-action-dialog.tsx    |  26 ++---
 .../src/dialogs/coordinator-dynamic-config.tsx     |  19 ++--
 web-console/src/dialogs/lookup-edit-dialog.tsx     |  48 ++++----
 web-console/src/dialogs/retention-dialog.tsx       |  12 +-
 web-console/src/dialogs/snitch-dialog.tsx          |  14 +--
 web-console/src/dialogs/spec-dialog.tsx            |  19 ++--
 web-console/src/entry.ts                           |   7 +-
 web-console/src/singletons/toaster.ts              |   2 +-
 web-console/src/utils/general.tsx                  |  14 +--
 web-console/src/utils/query-manager.tsx            |   6 +-
 web-console/src/utils/rune-decoder.tsx             |   7 +-
 web-console/src/views/datasource-view.tsx          |  36 +++---
 web-console/src/views/home-view.tsx                |  27 ++---
 web-console/src/views/lookups-view.tsx             |  86 +++++++-------
 web-console/src/views/segments-view.tsx            |  28 ++---
 web-console/src/views/servers-view.tsx             |  37 +++---
 web-console/src/views/sql-view.tsx                 |  19 ++--
 web-console/src/views/tasks-view.tsx               |  43 +++----
 web-console/tslint.json                            | 125 +++++++++++++++++++++
 web-console/webpack.config.js                      |  14 +++
 33 files changed, 719 insertions(+), 349 deletions(-)

diff --git a/web-console/package-lock.json b/web-console/package-lock.json
index 8c5035a..5c60be7 100644
--- a/web-console/package-lock.json
+++ b/web-console/package-lock.json
@@ -944,6 +944,65 @@
         "is-buffer": "^1.1.5"
       }
     },
+    "babel-code-frame": {
+      "version": "6.26.0",
+      "resolved": 
"https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz";,
+      "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
+      "dev": true,
+      "requires": {
+        "chalk": "^1.1.3",
+        "esutils": "^2.0.2",
+        "js-tokens": "^3.0.2"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "2.1.1",
+          "resolved": 
"https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz";,
+          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+          "dev": true
+        },
+        "ansi-styles": {
+          "version": "2.2.1",
+          "resolved": 
"https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz";,
+          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+          "dev": true
+        },
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz";,
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^2.2.1",
+            "escape-string-regexp": "^1.0.2",
+            "has-ansi": "^2.0.0",
+            "strip-ansi": "^3.0.0",
+            "supports-color": "^2.0.0"
+          }
+        },
+        "js-tokens": {
+          "version": "3.0.2",
+          "resolved": 
"https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz";,
+          "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "3.0.1",
+          "resolved": 
"https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz";,
+          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^2.0.0"
+          }
+        },
+        "supports-color": {
+          "version": "2.0.0",
+          "resolved": 
"https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz";,
+          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+          "dev": true
+        }
+      }
+    },
     "babel-jest": {
       "version": "24.1.0",
       "resolved": 
"https://registry.npmjs.org/babel-jest/-/babel-jest-24.1.0.tgz";,
@@ -1372,6 +1431,12 @@
       "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
       "dev": true
     },
+    "builtin-modules": {
+      "version": "1.1.1",
+      "resolved": 
"https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz";,
+      "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
+      "dev": true
+    },
     "builtin-status-codes": {
       "version": "3.0.0",
       "resolved": 
"https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz";,
@@ -9636,6 +9701,58 @@
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz";,
       "integrity": 
"sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ=="
     },
+    "tslint": {
+      "version": "5.14.0",
+      "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.14.0.tgz";,
+      "integrity": 
"sha512-IUla/ieHVnB8Le7LdQFRGlVJid2T/gaJe5VkjzRVSRR6pA2ODYrnfR1hmxi+5+au9l50jBwpbBL34txgv4NnTQ==",
+      "dev": true,
+      "requires": {
+        "babel-code-frame": "^6.22.0",
+        "builtin-modules": "^1.1.1",
+        "chalk": "^2.3.0",
+        "commander": "^2.12.1",
+        "diff": "^3.2.0",
+        "glob": "^7.1.1",
+        "js-yaml": "^3.7.0",
+        "minimatch": "^3.0.4",
+        "mkdirp": "^0.5.1",
+        "resolve": "^1.3.2",
+        "semver": "^5.3.0",
+        "tslib": "^1.8.0",
+        "tsutils": "^2.29.0"
+      }
+    },
+    "tslint-loader": {
+      "version": "3.5.4",
+      "resolved": 
"https://registry.npmjs.org/tslint-loader/-/tslint-loader-3.5.4.tgz";,
+      "integrity": 
"sha512-jBHNNppXut6SgZ7CsTBh+6oMwVum9n8azbmcYSeMlsABhWWoHwjq631vIFXef3VSd75cCdX3rc6kstsB7rSVVw==",
+      "dev": true,
+      "requires": {
+        "loader-utils": "^1.0.2",
+        "mkdirp": "^0.5.1",
+        "object-assign": "^4.1.1",
+        "rimraf": "^2.4.4",
+        "semver": "^5.3.0"
+      }
+    },
+    "tslint-react": {
+      "version": "3.6.0",
+      "resolved": 
"https://registry.npmjs.org/tslint-react/-/tslint-react-3.6.0.tgz";,
+      "integrity": 
"sha512-AIv1QcsSnj7e9pFir6cJ6vIncTqxfqeFF3Lzh8SuuBljueYzEAtByuB6zMaD27BL0xhMEqsZ9s5eHuCONydjBw==",
+      "dev": true,
+      "requires": {
+        "tsutils": "^2.13.1"
+      }
+    },
+    "tsutils": {
+      "version": "2.29.0",
+      "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz";,
+      "integrity": 
"sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
+      "dev": true,
+      "requires": {
+        "tslib": "^1.8.1"
+      }
+    },
     "tty-browserify": {
       "version": "0.0.0",
       "resolved": 
"https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz";,
diff --git a/web-console/package.json b/web-console/package.json
index 201100c..fcde0ad 100644
--- a/web-console/package.json
+++ b/web-console/package.json
@@ -70,6 +70,9 @@
     "ts-jest": "^23.10.5",
     "ts-loader": "^5.3.3",
     "ts-node": "^8.0.2",
+    "tslint": "^5.14.0",
+    "tslint-loader": "^3.5.4",
+    "tslint-react": "^3.6.0",
     "typescript": "^3.2.4",
     "webpack": "^4.29.0",
     "webpack-cli": "^3.2.1",
diff --git a/web-console/script/create-sql-function-doc 
b/web-console/script/create-sql-function-doc
index 146a39e..0142178 100755
--- a/web-console/script/create-sql-function-doc
+++ b/web-console/script/create-sql-function-doc
@@ -21,8 +21,35 @@ writefile='lib/sql-function-doc.ts'
 
 > "$writefile"
 
-echo -e "// This file is auto generated and should not be modified\n" > 
"$writefile"
-echo -e 'export const SQLFunctionDoc: any[] = [' >> "$writefile"
+cat > "$writefile" <<- EOM
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This file is auto generated and should not be modified
+
+export interface FunctionDescription {
+  syntax: string;
+  description: string;
+}
+
+/* tslint:disable */
+export const SQLFunctionDoc: FunctionDescription[] = [
+EOM
 
 isFunction=false
 
@@ -39,12 +66,12 @@ while read -r line; do
             description=$(echo $line | grep -o '`|.*.|')
             description=${description//\"/\'}
             description=${description:2:${#description}-4}
-            echo -e "   {" >> "$writefile"
-            echo -e "       syntax: \"$syntax\"," >> "$writefile"
-            echo -e "       description: \"$description\"" >> "$writefile"
-            echo -e "   }," >> "$writefile"
+            echo -e "  {" >> "$writefile"
+            echo -e "    syntax: \"$syntax\"," >> "$writefile"
+            echo -e "    description: \"$description\"" >> "$writefile"
+            echo -e "  }," >> "$writefile"
         fi
     fi
 done < "$readfile"
 
-echo -e ']' >> "$writefile"
\ No newline at end of file
+echo -e '];' >> "$writefile"
diff --git a/web-console/src/singletons/toaster.ts 
b/web-console/src/bootstrap/a-shim-for-react-props.ts
similarity index 71%
copy from web-console/src/singletons/toaster.ts
copy to web-console/src/bootstrap/a-shim-for-react-props.ts
index 3f4998e..844e0f3 100644
--- a/web-console/src/singletons/toaster.ts
+++ b/web-console/src/bootstrap/a-shim-for-react-props.ts
@@ -16,9 +16,10 @@
  * limitations under the License.
  */
 
-import { Position, Toaster } from "@blueprintjs/core";
+// Trick blueprint 1.0.1 into accepting React 16 as React 15.
+// This is broken into its own file to make linting and import sorting easy
+// This file "a" to make sure it is imported before console-application in 
entry.ts
 
-export const AppToaster = Toaster.create({
-  className: "recipe-toaster",
-  position: Position.TOP,
-});
+// tslint:disable
+import * as React from 'react';
+(React as any).PropTypes = require('prop-types');
diff --git a/web-console/src/singletons/react-table-defaults.tsx 
b/web-console/src/bootstrap/react-table-defaults.tsx
similarity index 100%
rename from web-console/src/singletons/react-table-defaults.tsx
rename to web-console/src/bootstrap/react-table-defaults.tsx
index 86cfd6d..edb776c 100644
--- a/web-console/src/singletons/react-table-defaults.tsx
+++ b/web-console/src/bootstrap/react-table-defaults.tsx
@@ -16,10 +16,10 @@
  * limitations under the License.
  */
 
-
+import { Button } from "@blueprintjs/core";
 import * as React from 'react';
 import { Filter, ReactTableDefaults } from "react-table";
-import { Button } from "@blueprintjs/core";
+
 import { Loader } from '../components/loader';
 import { countBy, makeTextFilter } from '../utils';
 
diff --git a/web-console/src/components/auto-form.tsx 
b/web-console/src/components/auto-form.tsx
index 39e8baa..6f86a41 100644
--- a/web-console/src/components/auto-form.tsx
+++ b/web-console/src/components/auto-form.tsx
@@ -16,11 +16,10 @@
  * limitations under the License.
  */
 
-import { resolveSrv } from 'dns';
-import * as React from 'react';
-import axios from 'axios';
 import { InputGroup } from "@blueprintjs/core";
-import { HTMLSelect, FormGroup, NumericInput, TagInput } from 
"../components/filler";
+import * as React from 'react';
+
+import { FormGroup, HTMLSelect, NumericInput, TagInput } from 
"../components/filler";
 
 interface Field {
   name: string;
@@ -31,8 +30,8 @@ interface Field {
 
 export interface AutoFormProps<T> extends React.Props<any> {
   fields: Field[];
-  model: T | null,
-  onChange: (newValue: T) => void
+  model: T | null;
+  onChange: (newValue: T) => void;
 }
 
 export interface AutoFormState<T> {
@@ -48,7 +47,7 @@ export class AutoForm<T> extends 
React.Component<AutoFormProps<T>, AutoFormState
   constructor(props: AutoFormProps<T>) {
     super(props);
     this.state = {
-    }
+    };
   }
 
   private renderNumberInput(field: Field): JSX.Element {
@@ -97,7 +96,7 @@ export class AutoForm<T> extends 
React.Component<AutoFormProps<T>, AutoFormState
     >
       <option value="True">True</option>
       <option value="False">False</option>
-    </HTMLSelect>
+    </HTMLSelect>;
   }
 
   private renderStringArrayInput(field: Field): JSX.Element {
@@ -127,7 +126,7 @@ export class AutoForm<T> extends 
React.Component<AutoFormProps<T>, AutoFormState
     const label = field.label || AutoForm.makeLabelName(field.name);
     return <FormGroup label={label} key={field.name}>
       {this.renderFieldInput(field)}
-    </FormGroup>
+    </FormGroup>;
   }
 
   render() {
@@ -135,6 +134,6 @@ export class AutoForm<T> extends 
React.Component<AutoFormProps<T>, AutoFormState
 
     return <div className="auto-form">
       {model && fields.map(field => this.renderField(field))}
-    </div>
+    </div>;
   }
 }
diff --git a/web-console/src/components/filler.tsx 
b/web-console/src/components/filler.tsx
index a0685f6..e20cd77 100644
--- a/web-console/src/components/filler.tsx
+++ b/web-console/src/components/filler.tsx
@@ -17,10 +17,10 @@
  */
 
 import { Button } from '@blueprintjs/core';
-import * as React from 'react';
 import classNames from 'classnames';
-import './filler.scss';
+import * as React from 'react';
 
+import './filler.scss';
 
 export const IconNames = {
   ERROR: "error" as "error",
@@ -103,16 +103,15 @@ export class FormGroup extends React.Component<{ 
className?: string, label?: str
   render() {
     const { className, label, children } = this.props;
     return <div className={classNames("form-group", className)}>
-      { label ? <Label>{label}</Label> : null }
+      {label ? <Label>{label}</Label> : null}
       {children}
     </div>;
   }
 }
 
-
 export const Alignment = {
   LEFT: "left" as "left",
-  RIGHT: "right" as "right",
+  RIGHT: "right" as "right"
 };
 export type Alignment = typeof Alignment[keyof typeof Alignment];
 
@@ -166,7 +165,7 @@ export interface NumericInputProps {
   min?: number;
   max?: number;
   stepSize?: number;
-  majorStepSize?: number
+  majorStepSize?: number;
 }
 
 export class NumericInput extends React.Component<NumericInputProps, { 
stringValue: string }> {
@@ -174,20 +173,20 @@ export class NumericInput extends 
React.Component<NumericInputProps, { stringVal
   static defaultProps = {
     stepSize: 1,
     majorStepSize: 10
-  }
+  };
 
   constructor(props: NumericInputProps) {
     super(props);
     this.state = {
       stringValue: typeof props.value === 'number' ? String(props.value) : ''
-    }
+    };
   }
 
   private constrain(n: number): number {
     const { min, max } = this.props;
     if (typeof min === 'number') n = Math.max(n, min);
     if (typeof max === 'number') n = Math.min(n, max);
-    return n
+    return n;
   }
 
   private handleChange = (e: any) => {
@@ -236,13 +235,13 @@ export class TagInput extends 
React.Component<TagInputProps, { stringValue: stri
     super(props);
     this.state = {
       stringValue: Array.isArray(props.values) ? props.values.join(', ') : ''
-    }
+    };
   }
 
   handleChange = (e: any) => {
-    let stringValue = e.target.value;
-    let newValues = stringValue.split(',').map((v: string) => v.trim());
-    let newValuesFiltered = newValues.filter(Boolean);
+    const stringValue = e.target.value;
+    const newValues = stringValue.split(',').map((v: string) => v.trim());
+    const newValuesFiltered = newValues.filter(Boolean);
     this.setState({
       stringValue: newValues.length === newValuesFiltered.length ? 
newValues.join(', ') : stringValue
     });
diff --git a/web-console/src/components/header-bar.tsx 
b/web-console/src/components/header-bar.tsx
index b4e4dad..9eea252 100644
--- a/web-console/src/components/header-bar.tsx
+++ b/web-console/src/components/header-bar.tsx
@@ -16,13 +16,13 @@
  * limitations under the License.
  */
 
-import * as React from 'react';
+import { AnchorButton, Button, Classes, Menu, MenuItem, Popover, Position } 
from "@blueprintjs/core";
 import classNames from 'classnames';
-import { Button, Classes, AnchorButton, Popover, Position, Menu, MenuItem } 
from "@blueprintjs/core";
-import { IconNames, NavbarGroup, Alignment, NavbarDivider, Navbar } from 
"../components/filler";
+import * as React from 'react';
+
+import { Alignment, IconNames, Navbar, NavbarDivider, NavbarGroup } from 
"../components/filler";
 import { AboutDialog } from "../dialogs/about-dialog";
 import { CoordinatorDynamicConfigDialog } from 
'../dialogs/coordinator-dynamic-config';
-import "./header-bar.scss";
 import {
   DRUID_DOCS,
   DRUID_GITHUB,
@@ -31,6 +31,8 @@ import {
   LEGACY_OVERLORD_CONSOLE
 } from '../variables';
 
+import "./header-bar.scss";
+
 export type HeaderActiveTab = null | 'datasources' | 'segments' | 'tasks' | 
'servers' | 'sql' | 'lookups';
 
 export interface HeaderBarProps extends React.Props<any> {
@@ -54,30 +56,48 @@ export class HeaderBar extends 
React.Component<HeaderBarProps, HeaderBarState> {
   renderLogo() {
     return <div className="logo">
       <svg version="1.1" xmlns="http://www.w3.org/2000/svg"; viewBox="0 0 288 
134">
-        <path fill="#FFFFFF" 
d="M136.7,67.5c0.5-6.1,5-10.4,10.6-10.4c3.9,0,6.5,2,7.4,4.3l1.1-12.4c0-0.1,0.3-0.2,0.7-0.2
+        <path
+          fill="#FFFFFF"
+          
d="M136.7,67.5c0.5-6.1,5-10.4,10.6-10.4c3.9,0,6.5,2,7.4,4.3l1.1-12.4c0-0.1,0.3-0.2,0.7-0.2
                 
c0.7,0,1.3,0.4,1.2,2l-2.3,25.9c-0.1,0.7-0.5,1-1,1h-0.2c-0.6,0-0.9-0.3-0.8-1l0.3-3.2c-1.7,2.7-4.5,4.5-8.3,4.5
                 C139.9,77.9,136.2,73.7,136.7,67.5z 
M154,68.9l0.4-4.7c-0.9-3.3-3.3-5.4-7.2-5.4c-4.5,0-8.1,3.6-8.5,8.6
-                c-0.4,5.1,2.5,8.7,6.9,8.7C150,76.1,153.7,72.9,154,68.9z"/>
-        <path fill="#FFFFFF" 
d="M161.2,76.6l1.7-19.1c0,0,0.3-0.2,0.7-0.2c0.7,0,1.3,0.4,1.1,2l-0.2,2.5c1.1-3.3,3.3-4.8,6-4.8
+                c-0.4,5.1,2.5,8.7,6.9,8.7C150,76.1,153.7,72.9,154,68.9z"
+        />
+        <path
+          fill="#FFFFFF"
+          
d="M161.2,76.6l1.7-19.1c0,0,0.3-0.2,0.7-0.2c0.7,0,1.3,0.4,1.1,2l-0.2,2.5c1.1-3.3,3.3-4.8,6-4.8
                 
c1.6,0,2.7,0.7,2.6,1.7c-0.1,0.8-0.6,1.1-0.7,1.1c-0.5-0.5-1.3-0.8-2.3-0.8c-3.6,0-5.6,3.6-6.1,9l-0.8,8.7c-0.1,0.7-0.5,1-1,1
-                h-0.2C161.5,77.6,161.2,77.4,161.2,76.6z"/>
-        <path fill="#FFFFFF" 
d="M175.6,69l0.9-10.7c0.1-0.8,0.5-1,1-1h0.3c0.5,0,0.9,0.2,0.8,1l-0.9,10.5c-0.4,4.4,1.5,7.2,5.5,7.2
+                h-0.2C161.5,77.6,161.2,77.4,161.2,76.6z"
+        />
+        <path
+          fill="#FFFFFF"
+          
d="M175.6,69l0.9-10.7c0.1-0.8,0.5-1,1-1h0.3c0.5,0,0.9,0.2,0.8,1l-0.9,10.5c-0.4,4.4,1.5,7.2,5.5,7.2
                 
c3.3,0,6-1.9,7.5-4.7l1.1-13c0.1-0.8,0.5-1,1-1h0.3c0.5,0,0.9,0.2,0.8,1l-1.7,19.1c0,0-0.4,0.2-0.7,0.2c-0.7,0-1.2-0.4-1.1-2
-                
l0.2-1.8c-1.6,2.4-4.2,4.1-7.6,4.1C177.6,77.9,175.2,74.4,175.6,69z"/>
-        <path fill="#FFFFFF" 
d="M200.1,50.7c0.1-1,0.6-1.4,1.6-1.4c0.9,0,1.4,0.5,1.3,1.4c-0.1,0.9-0.6,1.4-1.6,1.4
+                
l0.2-1.8c-1.6,2.4-4.2,4.1-7.6,4.1C177.6,77.9,175.2,74.4,175.6,69z"
+        />
+        <path
+          fill="#FFFFFF"
+          
d="M200.1,50.7c0.1-1,0.6-1.4,1.6-1.4c0.9,0,1.4,0.5,1.3,1.4c-0.1,0.9-0.6,1.4-1.6,1.4
                 C200.5,52.1,200,51.6,200.1,50.7z 
M198.2,76.6l1.6-18.3c0.1-0.8,0.5-1,1-1h0.3c0.5,0,0.9,0.2,0.8,1l-1.6,18.3
-                c-0.1,0.8-0.5,1-1,1H199C198.5,77.6,198.2,77.4,198.2,76.6z"/>
-        <path fill="#FFFFFF" 
d="M205.8,67.5c0.5-6.1,5-10.4,10.6-10.4c3.9,0,6.5,2,7.4,4.3l1.1-12.4c0-0.1,0.3-0.2,0.7-0.2
+                c-0.1,0.8-0.5,1-1,1H199C198.5,77.6,198.2,77.4,198.2,76.6z"
+        />
+        <path
+          fill="#FFFFFF"
+          
d="M205.8,67.5c0.5-6.1,5-10.4,10.6-10.4c3.9,0,6.5,2,7.4,4.3l1.1-12.4c0-0.1,0.3-0.2,0.7-0.2
                 
c0.7,0,1.3,0.4,1.2,2l-2.3,25.9c-0.1,0.7-0.5,1-1,1h-0.2c-0.5,0-0.9-0.3-0.8-1l0.3-3.2c-1.7,2.7-4.5,4.5-8.3,4.5
                 C209,77.9,205.2,73.7,205.8,67.5z 
M223.1,68.9l0.4-4.7c-0.9-3.3-3.3-5.4-7.2-5.4c-4.5,0-8.1,3.6-8.5,8.6
-                c-0.4,5.1,2.5,8.7,6.9,8.7C219,76.1,222.7,72.9,223.1,68.9z"/>
-        <path fill="#2CEEFB" 
d="M96.2,89.8h-2.7c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3h2.7c11.5,0,23.8-7.4,23.8-23.7
+                c-0.4,5.1,2.5,8.7,6.9,8.7C219,76.1,222.7,72.9,223.1,68.9z"
+        />
+        <path
+          fill="#2CEEFB"
+          
d="M96.2,89.8h-2.7c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3h2.7c11.5,0,23.8-7.4,23.8-23.7
                 
c0-9.1-6.9-15.8-16.4-15.8H80c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3h23.6c5.3,0,10.1,1.9,13.6,5.3
                 
c3.5,3.4,5.4,8,5.4,13.1c0,6.6-2.3,13-6.3,17.7C111.5,86.8,104.5,89.8,96.2,89.8z 
M87.1,89.8h-5.8c-0.7,0-1.3-0.6-1.3-1.3
                 
c0-0.7,0.6-1.3,1.3-1.3h5.8c0.7,0,1.3,0.6,1.3,1.3C88.4,89.2,87.8,89.8,87.1,89.8z 
M97.7,79.5h-26c-0.7,0-1.3-0.6-1.3-1.3
                 
c0-0.7,0.6-1.3,1.3-1.3h26c7.5,0,11.5-5.8,11.5-11.5c0-4.2-3.2-7.3-7.7-7.3h-26c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3
                 
h26c5.9,0,10.3,4.3,10.3,9.9c0,3.7-1.3,7.2-3.7,9.8C105.5,78,101.9,79.5,97.7,79.5z
 M69.2,58h-6.3c-0.7,0-1.3-0.6-1.3-1.3
-                
c0-0.7,0.6-1.3,1.3-1.3h6.3c0.7,0,1.3,0.6,1.3,1.3C70.5,57.4,69.9,58,69.2,58z"/>
+                
c0-0.7,0.6-1.3,1.3-1.3h6.3c0.7,0,1.3,0.6,1.3,1.3C70.5,57.4,69.9,58,69.2,58z"
+        />
       </svg>
     </div>;
   }
diff --git a/web-console/src/components/loader.tsx 
b/web-console/src/components/loader.tsx
index cb9ef81..3a122b9 100644
--- a/web-console/src/components/loader.tsx
+++ b/web-console/src/components/loader.tsx
@@ -17,6 +17,7 @@
  */
 
 import * as React from 'react';
+
 import './loader.scss';
 
 export interface LoaderProps extends React.Props<any> {
@@ -31,23 +32,35 @@ export class Loader extends React.Component<LoaderProps, 
LoaderState> {
 
   render() {
     const { loadingText, loading } = this.props;
-    if (loading === false) return null;
+    if (!loading) return null;
 
     return <div className="loader">
       <div className="loader-logo">
         <svg viewBox="0 0 100 100">
-          <path className="one" 
d="M54.2,69.8h-2.7c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3h2.7c11.5,0,23.8-7.4,23.8-23.7
+          <path
+            className="one"
+            
d="M54.2,69.8h-2.7c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3h2.7c11.5,0,23.8-7.4,23.8-23.7
           
c0-9.1-6.9-15.8-16.4-15.8H38c-0.7,0-1.3-0.6-1.3-1.3s0.6-1.3,1.3-1.3h23.6c5.3,0,10.1,1.9,13.6,5.3c3.5,3.4,5.4,8,5.4,13.1
-          c0,6.6-2.3,13-6.3,17.7C69.5,66.8,62.5,69.8,54.2,69.8z"/>
-          <path className="two" 
d="M55.7,59.5h-26c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3h26c7.5,0,11.5-5.8,11.5-11.5
+          c0,6.6-2.3,13-6.3,17.7C69.5,66.8,62.5,69.8,54.2,69.8z"
+          />
+          <path
+            className="two"
+            
d="M55.7,59.5h-26c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3h26c7.5,0,11.5-5.8,11.5-11.5
             
c0-4.2-3.2-7.3-7.7-7.3h-26c-0.7,0-1.3-0.6-1.3-1.3s0.6-1.3,1.3-1.3h26c5.9,0,10.3,4.3,10.3,9.9c0,3.7-1.3,7.2-3.7,9.8
-            C63.5,58,59.9,59.5,55.7,59.5z"/>
-          <path className="three" 
d="M27.2,38h-6.3c-0.7,0-1.3-0.6-1.3-1.3s0.6-1.3,1.3-1.3h6.3c0.7,0,1.3,0.6,1.3,1.3S27.9,38,27.2,38z"/>
-          <path className="four" 
d="M45.1,69.8h-5.8c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3h5.8c0.7,0,1.3,0.6,1.3,1.3
-            C46.4,69.2,45.8,69.8,45.1,69.8z"/>
+            C63.5,58,59.9,59.5,55.7,59.5z"
+          />
+          <path
+            className="three"
+            
d="M27.2,38h-6.3c-0.7,0-1.3-0.6-1.3-1.3s0.6-1.3,1.3-1.3h6.3c0.7,0,1.3,0.6,1.3,1.3S27.9,38,27.2,38z"
+          />
+          <path
+            className="four"
+            
d="M45.1,69.8h-5.8c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3h5.8c0.7,0,1.3,0.6,1.3,1.3
+            C46.4,69.2,45.8,69.8,45.1,69.8z"
+          />
         </svg>
         {loadingText ? <div className="label">{loadingText}</div> : null}
       </div>
-    </div>
+    </div>;
   }
 }
diff --git a/web-console/src/components/rule-editor.tsx 
b/web-console/src/components/rule-editor.tsx
index a431ce6..f13f046 100644
--- a/web-console/src/components/rule-editor.tsx
+++ b/web-console/src/components/rule-editor.tsx
@@ -16,10 +16,12 @@
  * limitations under the License.
  */
 
-import * as React from 'react';
+import { Button, Collapse, InputGroup } from "@blueprintjs/core";
 import axios from 'axios';
-import { Button, InputGroup, Collapse } from "@blueprintjs/core";
-import { IconNames, FormGroup, HTMLSelect, Card, ControlGroup, NumericInput, 
TagInput } from "../components/filler";
+import * as React from 'react';
+
+import { Card, ControlGroup, FormGroup, HTMLSelect, IconNames, NumericInput, 
TagInput } from "../components/filler";
+
 import './rule-editor.scss';
 
 export interface Rule {
@@ -107,7 +109,7 @@ export class RuleEditor extends 
React.Component<RuleEditorProps, RuleEditorState
     super(props);
     this.state = {
       isOpen: true
-    }
+    };
   }
 
   private removeTier = (key: string) => {
@@ -126,9 +128,9 @@ export class RuleEditor extends 
React.Component<RuleEditorProps, RuleEditorState
     let newTierName = tiers[0];
 
     if (rule.tieredReplicants) {
-      for (let i = 0; i < tiers.length; i++) {
-        if (rule.tieredReplicants[tiers[i]] === undefined) {
-          newTierName = tiers[i];
+      for (const tier of tiers) {
+        if (rule.tieredReplicants[tier] === undefined) {
+          newTierName = tier;
           break;
         }
       }
@@ -160,7 +162,7 @@ export class RuleEditor extends 
React.Component<RuleEditorProps, RuleEditorState
         />
         <Button className="pt-minimal" style={{pointerEvents: 
'none'}}>Tier:</Button>
         <HTMLSelect
-          fill={true}
+          fill
           value={tier}
           onChange={(e: any) => onChange(RuleEditor.changeTier(rule, tier, 
e.target.value))}
         >
@@ -194,7 +196,7 @@ export class RuleEditor extends 
React.Component<RuleEditorProps, RuleEditorState
     return <FormGroup label="Colocated datasources:">
       <TagInput
         values={rule.colocatedDataSources || []}
-        onChange={(v: any) => 
onChange(RuleEditor.changeColocatedDataSources(rule, v)) }
+        onChange={(v: any) => 
onChange(RuleEditor.changeColocatedDataSources(rule, v))}
         fill
       />
     </FormGroup>;
@@ -240,21 +242,21 @@ export class RuleEditor extends 
React.Component<RuleEditorProps, RuleEditorState
                 <option value="ByPeriod">by period</option>
                 <option value="ByInterval">by interval</option>
               </HTMLSelect>
-              { ruleTimeType === 'ByPeriod' && <InputGroup value={rule.period 
|| ''} onChange={(e: any) => onChange(RuleEditor.changePeriod(rule, 
e.target.value as any))}/>}
-              { ruleTimeType === 'ByInterval' && <InputGroup 
value={rule.interval || ''} onChange={(e: any) => 
onChange(RuleEditor.changeInterval(rule, e.target.value as any))}/>}
+              {ruleTimeType === 'ByPeriod' && <InputGroup value={rule.period 
|| ''} onChange={(e: any) => onChange(RuleEditor.changePeriod(rule, 
e.target.value as any))}/>}
+              {ruleTimeType === 'ByInterval' && <InputGroup 
value={rule.interval || ''} onChange={(e: any) => 
onChange(RuleEditor.changeInterval(rule, e.target.value as any))}/>}
             </ControlGroup>
           </FormGroup>
           {
             ruleLoadType === 'load' &&
             <FormGroup>
-              { this.renderTiers() }
-              { this.renderTierAdder() }
+              {this.renderTiers()}
+              {this.renderTierAdder()}
             </FormGroup>
           }
           {
             ruleLoadType === 'broadcast' &&
             <FormGroup>
-              { this.renderColocatedDataSources() }
+              {this.renderColocatedDataSources()}
             </FormGroup>
           }
         </Card>
diff --git a/web-console/src/components/sql-control.tsx 
b/web-console/src/components/sql-control.tsx
index a2d21b5..c63b880 100644
--- a/web-console/src/components/sql-control.tsx
+++ b/web-console/src/components/sql-control.tsx
@@ -16,21 +16,24 @@
  * limitations under the License.
  */
 
-import * as React from 'react';
-import * as ReactDOMServer from 'react-dom/server';
+import { Button, Checkbox, Classes, Intent, Popover, Position } from 
"@blueprintjs/core";
 import axios from "axios";
-import * as classNames from 'classnames';
-import * as ace from 'brace'
-import AceEditor from "react-ace";
-import 'brace/mode/sql';
+import * as ace from 'brace';
+import 'brace/ext/language_tools';
 import 'brace/mode/hjson';
+import 'brace/mode/sql';
 import 'brace/theme/solarized_dark';
-import 'brace/ext/language_tools';
-import {Intent, Button, Popover, Checkbox, Classes, Position} from 
"@blueprintjs/core";
+import * as classNames from 'classnames';
+import * as React from 'react';
+import AceEditor from "react-ace";
+import * as ReactDOMServer from 'react-dom/server';
+
 import { SQLFunctionDoc } from "../../lib/sql-function-doc";
+import { AppToaster } from "../singletons/toaster";
+
 import { IconNames } from './filler';
-import './sql-control.scss'
-import {AppToaster} from "../singletons/toaster";
+
+import './sql-control.scss';
 
 const langTools = ace.acequire('ace/ext/language_tools');
 
@@ -55,9 +58,8 @@ export class SqlControl extends 
React.Component<SqlControlProps, SqlControlState
     };
   }
 
-
-  private addDatasourceAutoCompleter = async (): Promise<any> =>{
-    const datasourceResp = await axios.post("/druid/v2/sql", { query: `SELECT 
datasource FROM sys.segments GROUP BY 1`})
+  private addDatasourceAutoCompleter = async (): Promise<any> => {
+    const datasourceResp = await axios.post("/druid/v2/sql", { query: `SELECT 
datasource FROM sys.segments GROUP BY 1`});
     const datasourceList: any[] = datasourceResp.data.map((d: any) => {
       const datasourceName: string = d.datasource;
       return {
@@ -68,7 +70,7 @@ export class SqlControl extends 
React.Component<SqlControlProps, SqlControlState
     });
 
     const completer = {
-      getCompletions: (editor:any , session: any, pos: any, prefix: any, 
callback: any) => {
+      getCompletions: (editor: any, session: any, pos: any, prefix: any, 
callback: any) => {
         callback(null, datasourceList);
       }
     };
@@ -77,7 +79,7 @@ export class SqlControl extends 
React.Component<SqlControlProps, SqlControlState
   }
 
   private addColumnNameAutoCompleter = async (): Promise<any> => {
-    const columnNameResp = await axios.post("/druid/v2/sql", {query: `SELECT 
COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'druid'`})
+    const columnNameResp = await axios.post("/druid/v2/sql", {query: `SELECT 
COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'druid'`});
     const columnNameList: any[] = columnNameResp.data.map((d: any) => {
       const columnName: string = d.COLUMN_NAME;
       return {
@@ -88,7 +90,7 @@ export class SqlControl extends 
React.Component<SqlControlProps, SqlControlState
     });
 
     const completer = {
-      getCompletions: (editor:any , session: any, pos: any, prefix: any, 
callback: any) => {
+      getCompletions: (editor: any, session: any, pos: any, prefix: any, 
callback: any) => {
         callback(null, columnNameList);
       }
     };
@@ -97,9 +99,9 @@ export class SqlControl extends 
React.Component<SqlControlProps, SqlControlState
   }
 
   private addFunctionAutoCompleter = (): void => {
-    const functionList: any[]= SQLFunctionDoc.map((entry: any) => {
-      let funcName: string = entry.syntax.replace(/\(.*\)/,"()");
-      if (!funcName.includes("(")) funcName = funcName.substr(0,10);
+    const functionList: any[] = SQLFunctionDoc.map((entry: any) => {
+      let funcName: string = entry.syntax.replace(/\(.*\)/, "()");
+      if (!funcName.includes("(")) funcName = funcName.substr(0, 10);
       return {
         value: funcName,
         score: 80,
@@ -107,22 +109,22 @@ export class SqlControl extends 
React.Component<SqlControlProps, SqlControlState
         syntax: entry.syntax,
         description: entry.description,
         completer: {
-          insertMatch: (editor:any, data:any) => {
+          insertMatch: (editor: any, data: any) => {
             editor.completer.insertMatch({value: data.caption});
             const pos = editor.getCursorPosition();
-            editor.gotoLine(pos.row+1, pos.column-1);
+            editor.gotoLine(pos.row + 1, pos.column - 1);
           }
         }
       };
     });
 
     const completer = {
-      getCompletions: (editor:any , session: any, pos: any, prefix: any, 
callback: any) => {
+      getCompletions: (editor: any, session: any, pos: any, prefix: any, 
callback: any) => {
         callback(null, functionList);
       },
       getDocTooltip: (item: any) => {
         if (item.meta === "function") {
-          const functionName = item.caption.slice(0,-2);
+          const functionName = item.caption.slice(0, -2);
           item.docHTML = ReactDOMServer.renderToStaticMarkup((
             <div className={"function-doc"}>
               <div className={"function-doc-name"}><b>{functionName}</b></div>
@@ -133,7 +135,7 @@ export class SqlControl extends 
React.Component<SqlControlProps, SqlControlState
               <div><b>Description:</b></div>
               <div>{item.description}</div>
             </div>
-          ))
+          ));
         }
       }
     };
@@ -160,7 +162,7 @@ export class SqlControl extends 
React.Component<SqlControlProps, SqlControlState
   private handleChange = (newValue: string): void => {
     this.setState({
       query: newValue
-    })
+    });
   }
 
   render() {
@@ -186,7 +188,7 @@ export class SqlControl extends 
React.Component<SqlControlProps, SqlControlState
         theme="solarized_dark"
         name="ace-editor"
         onChange={this.handleChange}
-        focus={true}
+        focus
         fontSize={14}
         width={'100%'}
         height={"30vh"}
@@ -199,7 +201,7 @@ export class SqlControl extends 
React.Component<SqlControlProps, SqlControlState
           enableBasicAutocompletion: isRune ? false : autoCompleteOn,
           enableLiveAutocompletion: isRune ? false : autoCompleteOn,
           showLineNumbers: true,
-          tabSize: 2,
+          tabSize: 2
         }}
       />
       <div className="buttons">
@@ -213,4 +215,3 @@ export class SqlControl extends 
React.Component<SqlControlProps, SqlControlState
     </div>;
   }
 }
-
diff --git a/web-console/src/console-application.tsx 
b/web-console/src/console-application.tsx
index 9b61052..332783f 100644
--- a/web-console/src/console-application.tsx
+++ b/web-console/src/console-application.tsx
@@ -16,22 +16,23 @@
  * limitations under the License.
  */
 
+import { Intent } from "@blueprintjs/core";
 import axios from 'axios';
-import * as React from 'react';
 import * as classNames from 'classnames';
+import * as React from 'react';
 import { HashRouter, Route, Switch } from "react-router-dom";
-import { Intent } from "@blueprintjs/core";
+
+import { HeaderActiveTab, HeaderBar } from './components/header-bar';
 import { AppToaster } from './singletons/toaster';
-import { HeaderBar, HeaderActiveTab } from './components/header-bar';
-import { localStorageGet, localStorageSet } from './utils';
 import { DRUID_DOCS_SQL, LEGACY_COORDINATOR_CONSOLE, LEGACY_OVERLORD_CONSOLE } 
from './variables';
-import { HomeView } from './views/home-view';
 import { DatasourcesView } from './views/datasource-view';
+import { HomeView } from './views/home-view';
+import { LookupsView } from "./views/lookups-view";
 import { SegmentsView } from './views/segments-view';
 import { ServersView } from './views/servers-view';
-import { TasksView } from './views/tasks-view';
 import { SqlView } from './views/sql-view';
-import { LookupsView } from "./views/lookups-view";
+import { TasksView } from './views/tasks-view';
+
 import "./console-application.scss";
 
 export interface ConsoleApplicationProps extends React.Props<any> {
@@ -63,14 +64,16 @@ export class ConsoleApplication extends 
React.Component<ConsoleApplicationProps,
         iconName: 'error',
         intent: Intent.DANGER,
         timeout: 120000,
+        /* tslint:disable:jsx-alignment */
         message: <>
           It appears that the SQL endpoint is disabled. Either <a
           href={DRUID_DOCS_SQL}>enable the SQL endpoint</a> or use the old <a
           href={LEGACY_COORDINATOR_CONSOLE}>coordinator</a> and <a
           href={LEGACY_OVERLORD_CONSOLE}>overlord</a> consoles that do not 
rely on the SQL endpoint.
         </>
+        /* tslint:enable:jsx-alignment */
       });
-      return false
+      return false;
     }
     return true;
   }
@@ -142,30 +145,49 @@ export class ConsoleApplication extends 
React.Component<ConsoleApplicationProps,
     return <HashRouter hashType="noslash">
       <div className="console-application">
         <Switch>
-          <Route path="/datasources" component={() => {
-            return wrapInViewContainer('datasources', <DatasourcesView 
goToSql={this.goToSql} goToSegments={this.goToSegments}/>);
-          }} />
-          <Route path="/segments" component={() => {
-            return wrapInViewContainer('segments', <SegmentsView 
datasource={this.datasource} onlyUnavailable={this.onlyUnavailable} 
goToSql={this.goToSql}/>);
-          }} />
-          <Route path="/tasks" component={() => {
-            return wrapInViewContainer('tasks', <TasksView 
taskId={this.taskId} goToSql={this.goToSql} 
goToMiddleManager={this.goToMiddleManager}/>, true);
-          }} />
-          <Route path="/servers" component={() => {
-            return wrapInViewContainer('servers', <ServersView 
middleManager={this.middleManager} goToSql={this.goToSql} 
goToTask={this.goToTask}/>, true);
-          }} />
-          <Route path="/sql" component={() => {
-            return wrapInViewContainer('sql', <SqlView 
initSql={this.initSql}/>);
-          }} />
-          <Route path="/lookups" component={() => {
-            return wrapInViewContainer('lookups', <LookupsView />);
-          }} />
-          <Route component={() => {
-            return wrapInViewContainer(null, <HomeView/>)
-          }} />
+          <Route
+            path="/datasources"
+            component={() => {
+              return wrapInViewContainer('datasources', <DatasourcesView 
goToSql={this.goToSql} goToSegments={this.goToSegments}/>);
+            }}
+          />
+          <Route
+            path="/segments"
+            component={() => {
+              return wrapInViewContainer('segments', <SegmentsView 
datasource={this.datasource} onlyUnavailable={this.onlyUnavailable} 
goToSql={this.goToSql}/>);
+            }}
+          />
+          <Route
+            path="/tasks"
+            component={() => {
+              return wrapInViewContainer('tasks', <TasksView 
taskId={this.taskId} goToSql={this.goToSql} 
goToMiddleManager={this.goToMiddleManager}/>, true);
+            }}
+          />
+          <Route
+            path="/servers"
+            component={() => {
+              return wrapInViewContainer('servers', <ServersView 
middleManager={this.middleManager} goToSql={this.goToSql} 
goToTask={this.goToTask}/>, true);
+            }}
+          />
+          <Route
+            path="/sql"
+            component={() => {
+              return wrapInViewContainer('sql', <SqlView 
initSql={this.initSql}/>);
+            }}
+          />
+          <Route
+            path="/lookups"
+            component={() => {
+              return wrapInViewContainer('lookups', <LookupsView />);
+            }}
+          />
+          <Route
+            component={() => {
+              return wrapInViewContainer(null, <HomeView/>);
+            }}
+          />
         </Switch>
       </div>
     </HashRouter>;
   }
 }
-
diff --git a/web-console/src/dialogs/about-dialog.tsx 
b/web-console/src/dialogs/about-dialog.tsx
index ee1fe0b..570fc13 100644
--- a/web-console/src/dialogs/about-dialog.tsx
+++ b/web-console/src/dialogs/about-dialog.tsx
@@ -16,13 +16,14 @@
  * limitations under the License.
  */
 
+import { AnchorButton, Button, Classes, Dialog, Intent } from 
"@blueprintjs/core";
 import * as React from 'react';
-import { Button, Dialog, Classes, AnchorButton, Intent } from 
"@blueprintjs/core";
+
 import { IconNames } from "../components/filler";
 import { DRUID_COMMUNITY, DRUID_DEVELOPER_GROUP, DRUID_USER_GROUP, 
DRUID_WEBSITE } from '../variables';
 
 export interface AboutDialogProps extends React.Props<any> {
-  onClose: () => void
+  onClose: () => void;
 }
 
 export interface AboutDialogState {
@@ -37,6 +38,7 @@ export class AboutDialog extends 
React.Component<AboutDialogProps, AboutDialogSt
   render() {
     const { onClose } = this.props;
 
+    /* tslint:disable:jsx-alignment */
     return <Dialog
       iconName={IconNames.GRAPH}
       onClose={onClose}
@@ -75,5 +77,6 @@ export class AboutDialog extends 
React.Component<AboutDialogProps, AboutDialogSt
         </div>
       </div>
     </Dialog>;
+    /* tslint:enable:jsx-alignment */
   }
 }
diff --git a/web-console/src/dialogs/async-action-dialog.tsx 
b/web-console/src/dialogs/async-action-dialog.tsx
index 0dc5af1..bbab05c 100644
--- a/web-console/src/dialogs/async-action-dialog.tsx
+++ b/web-console/src/dialogs/async-action-dialog.tsx
@@ -16,25 +16,25 @@
  * limitations under the License.
  */
 
-import classNames from 'classnames';
-import * as React from 'react';
 import {
   Button,
-  InputGroup,
-  Dialog,
   Classes,
+  Dialog,
   Intent,
   ProgressBar
 } from "@blueprintjs/core";
-import { Icon, FormGroup, ButtonGroup, NumericInput, TagInput } from 
'../components/filler';
+import classNames from 'classnames';
+import * as React from 'react';
+
+import { ButtonGroup, FormGroup, Icon, NumericInput, TagInput } from 
'../components/filler';
 import { AppToaster } from '../singletons/toaster';
 
 export interface AsyncAlertDialogProps extends React.Props<any> {
-  action: null | (() => Promise<void>),
-  onClose: (success: boolean) => void,
+  action: null | (() => Promise<void>);
+  onClose: (success: boolean) => void;
   confirmButtonText: string;
   cancelButtonText?: string;
-  className?: string,
+  className?: string;
   icon?: string;
   intent?: Intent;
   successText: string;
@@ -42,7 +42,7 @@ export interface AsyncAlertDialogProps extends 
React.Props<any> {
 }
 
 export interface AsyncAlertDialogState {
-  working: boolean
+  working: boolean;
 }
 
 export class AsyncActionDialog extends React.Component<AsyncAlertDialogProps, 
AsyncAlertDialogState> {
@@ -59,7 +59,7 @@ export class AsyncActionDialog extends 
React.Component<AsyncAlertDialogProps, As
 
     this.setState({ working: true });
     try {
-      await action()
+      await action();
     } catch (e) {
       AppToaster.show({
         message: `${failText}: ${e.message}`,
@@ -92,8 +92,8 @@ export class AsyncActionDialog extends 
React.Component<AsyncAlertDialogProps, As
       onClose={handleClose}
     >
       <div className={Classes.ALERT_BODY}>
-        { icon && <Icon icon={icon} /> }
-        { !working && <div className={Classes.ALERT_CONTENTS}>{children}</div> 
}
+        {icon && <Icon icon={icon} />}
+        {!working && <div className={Classes.ALERT_CONTENTS}>{children}</div>}
       </div>
       {
         working ?
@@ -103,6 +103,6 @@ export class AsyncActionDialog extends 
React.Component<AsyncAlertDialogProps, As
             <Button text={cancelButtonText || 'Cancel'} onClick={handleClose}/>
           </div>
       }
-    </Dialog>
+    </Dialog>;
   }
 }
diff --git a/web-console/src/dialogs/coordinator-dynamic-config.tsx 
b/web-console/src/dialogs/coordinator-dynamic-config.tsx
index 474b0e2..59ee165 100644
--- a/web-console/src/dialogs/coordinator-dynamic-config.tsx
+++ b/web-console/src/dialogs/coordinator-dynamic-config.tsx
@@ -17,17 +17,20 @@
  */
 
 import { Intent } from '@blueprintjs/core';
-import * as React from 'react';
 import axios from 'axios';
-import { AppToaster } from '../singletons/toaster';
-import { IconNames } from '../components/filler';
+import * as React from 'react';
+
 import { AutoForm } from '../components/auto-form';
+import { IconNames } from '../components/filler';
+import { AppToaster } from '../singletons/toaster';
 import { getDruidErrorMessage } from '../utils';
+
 import { SnitchDialog } from './snitch-dialog';
+
 import './coordinator-dynamic-config.scss';
 
 export interface CoordinatorDynamicConfigDialogProps extends React.Props<any> {
-  onClose: () => void
+  onClose: () => void;
 }
 
 export interface CoordinatorDynamicConfigDialogState {
@@ -39,7 +42,7 @@ export class CoordinatorDynamicConfigDialog extends 
React.Component<CoordinatorD
     super(props);
     this.state = {
       dynamicConfig: null
-    }
+    };
   }
 
   componentDidMount(): void {
@@ -50,7 +53,7 @@ export class CoordinatorDynamicConfigDialog extends 
React.Component<CoordinatorD
     let config: Record<string, any> | null = null;
     try {
       const configResp = await axios.get("/druid/coordinator/v1/config");
-      config = configResp.data
+      config = configResp.data;
     } catch (e) {
       AppToaster.show({
         iconName: IconNames.ERROR,
@@ -66,7 +69,7 @@ export class CoordinatorDynamicConfigDialog extends 
React.Component<CoordinatorD
 
   private saveClusterConfig = async (author: string, comment: string) => {
     const { onClose } = this.props;
-    let newState: any = this.state.dynamicConfig;
+    const newState: any = this.state.dynamicConfig;
     try {
       await axios.post("/druid/coordinator/v1/config", newState, {
         headers: {
@@ -158,6 +161,6 @@ export class CoordinatorDynamicConfigDialog extends 
React.Component<CoordinatorD
         model={dynamicConfig}
         onChange={m => this.setState({ dynamicConfig: m })}
       />
-    </SnitchDialog>
+    </SnitchDialog>;
   }
 }
diff --git a/web-console/src/dialogs/lookup-edit-dialog.tsx 
b/web-console/src/dialogs/lookup-edit-dialog.tsx
index 3375b94..e32ec5a 100644
--- a/web-console/src/dialogs/lookup-edit-dialog.tsx
+++ b/web-console/src/dialogs/lookup-edit-dialog.tsx
@@ -16,24 +16,26 @@
  * limitations under the License.
  */
 
+import { Button, Classes, Dialog, InputGroup, Intent } from 
"@blueprintjs/core";
 import * as React from "react";
-import {Button, Classes, Dialog, Intent, InputGroup } from "@blueprintjs/core";
-import "./lookup-edit-dialog.scss"
-import {validJson} from "../utils";
 import AceEditor from "react-ace";
-import {FormGroup} from "../components/filler";
+
+import { FormGroup } from "../components/filler";
+import { validJson } from "../utils";
+
+import "./lookup-edit-dialog.scss";
 
 export interface LookupEditDialogProps extends React.Props<any> {
-  isOpen: boolean,
-  onClose: () => void,
-  onSubmit: () => void,
-  onChange: (field: string, value: string) => void
-  lookupName: string,
-  lookupTier: string,
-  lookupVersion: string,
-  lookupSpec: string,
-  isEdit: boolean,
-  allLookupTiers: string[]
+  isOpen: boolean;
+  onClose: () => void;
+  onSubmit: () => void;
+  onChange: (field: string, value: string) => void;
+  lookupName: string;
+  lookupTier: string;
+  lookupVersion: string;
+  lookupSpec: string;
+  isEdit: boolean;
+  allLookupTiers: string[];
 }
 
 export interface LookupEditDialogState {
@@ -45,7 +47,7 @@ export class LookupEditDialog extends 
React.Component<LookupEditDialogProps, Loo
     super(props);
     this.state = {
 
-    }
+    };
   }
 
   private addISOVersion = () => {
@@ -62,21 +64,21 @@ export class LookupEditDialog extends 
React.Component<LookupEditDialogProps, Loo
         <InputGroup
           value={lookupTier}
           onChange={(e: any) => onChange("lookupEditTier", e.target.value)}
-          disabled={true}
+          disabled
         />
-      </FormGroup>
+      </FormGroup>;
     } else {
       return <FormGroup className={"lookup-label"} label={"Tier:"}>
         <div className="pt-select">
-          <select disabled={isEdit} value={lookupTier} onChange={(e:any) => 
onChange("lookupEditTier", e.target.value)}>
+          <select disabled={isEdit} value={lookupTier} onChange={(e: any) => 
onChange("lookupEditTier", e.target.value)}>
             {
               allLookupTiers.map(tier => {
-                return <option key={tier} value={tier}>{tier}</option>
+                return <option key={tier} value={tier}>{tier}</option>;
               })
             }
           </select>
         </div>
-      </FormGroup>
+      </FormGroup>;
     }
   }
 
@@ -101,7 +103,7 @@ export class LookupEditDialog extends 
React.Component<LookupEditDialogProps, Loo
         />
       </FormGroup>
 
-      { this.renderTierInput() }
+      {this.renderTierInput()}
 
       <FormGroup className={"lookup-label"} label={"Version:"}>
         <InputGroup
@@ -131,7 +133,7 @@ export class LookupEditDialog extends 
React.Component<LookupEditDialogProps, Loo
         setOptions={{
           enableBasicAutocompletion: false,
           enableLiveAutocompletion: false,
-          tabSize: 2,
+          tabSize: 2
         }}
       />
 
@@ -151,4 +153,4 @@ export class LookupEditDialog extends 
React.Component<LookupEditDialogProps, Loo
       </div>
     </Dialog>;
   }
-}
\ No newline at end of file
+}
diff --git a/web-console/src/dialogs/retention-dialog.tsx 
b/web-console/src/dialogs/retention-dialog.tsx
index a6e02af..10d85f2 100644
--- a/web-console/src/dialogs/retention-dialog.tsx
+++ b/web-console/src/dialogs/retention-dialog.tsx
@@ -16,17 +16,19 @@
  * limitations under the License.
  */
 
-import * as React from 'react';
-import axios from 'axios';
 import { Button } from "@blueprintjs/core";
+import axios from 'axios';
+import * as React from 'react';
+
 import { FormGroup, IconNames } from '../components/filler';
-import { RuleEditor, Rule } from '../components/rule-editor';
+import { Rule, RuleEditor } from '../components/rule-editor';
+
 import { SnitchDialog } from './snitch-dialog';
 
 import './retention-dialog.scss';
 
 export function reorderArray<T>(items: T[], oldIndex: number, newIndex: 
number): T[] {
-  let newItems = items.concat();
+  const newItems = items.concat();
 
   if (newIndex > oldIndex) newIndex--;
 
@@ -111,7 +113,7 @@ export class RetentionDialog extends 
React.Component<RetentionDialogProps, Reten
       onDelete={() => this.onDeleteRule(index)}
       moveUp={index > 0 ? () => this.moveRule(index, -1) : null}
       moveDown={index < (currentRules || []).length - 1 ? () => 
this.moveRule(index, 2) : null}
-    />
+    />;
   }
 
   reset = () => {
diff --git a/web-console/src/dialogs/snitch-dialog.tsx 
b/web-console/src/dialogs/snitch-dialog.tsx
index d20971e..c01913e 100644
--- a/web-console/src/dialogs/snitch-dialog.tsx
+++ b/web-console/src/dialogs/snitch-dialog.tsx
@@ -16,17 +16,17 @@
  * limitations under the License.
  */
 
-import * as React from 'react';
 import {
   Button,
-  InputGroup,
+  Classes,
   Dialog,
   IDialogProps,
-  Classes,
-  Intent,
+  InputGroup,
+  Intent
 } from "@blueprintjs/core";
-import { IconNames, FormGroup } from '../components/filler';
+import * as React from 'react';
 
+import { FormGroup, IconNames } from '../components/filler';
 
 export interface SnitchDialogProps extends IDialogProps {
   onSave: (author: string, comment: string) => void;
@@ -50,7 +50,7 @@ export class SnitchDialog extends 
React.Component<SnitchDialogProps, SnitchDialo
       comment: "",
       author: "",
       saveDisabled: true
-    }
+    };
   }
 
   save = () => {
@@ -136,7 +136,7 @@ export class SnitchDialog extends 
React.Component<SnitchDialogProps, SnitchDialo
         ? <Button disabled={saveDisabled} text="Save" onClick={this.save} 
intent={Intent.PRIMARY as any} rightIconName={IconNames.TICK}/>
         : <Button disabled={saveDisabled} text="Next" 
onClick={this.goToFinalStep} intent={Intent.PRIMARY as any} 
rightIconName={IconNames.ARROW_RIGHT}/>
       }
-    </div>
+    </div>;
   }
 
   render() {
diff --git a/web-console/src/dialogs/spec-dialog.tsx 
b/web-console/src/dialogs/spec-dialog.tsx
index 8cd8065..9b65901 100644
--- a/web-console/src/dialogs/spec-dialog.tsx
+++ b/web-console/src/dialogs/spec-dialog.tsx
@@ -16,12 +16,13 @@
  * limitations under the License.
  */
 
-import * as React from "react";
 import { Button, Classes, Dialog, Intent } from "@blueprintjs/core";
-import "./spec-dialog.scss"
-import AceEditor from "react-ace";
+import "brace/mode/json";
 import "brace/theme/solarized_dark";
-import "brace/mode/json"
+import * as React from "react";
+import AceEditor from "react-ace";
+
+import "./spec-dialog.scss";
 
 export interface SpecDialogProps extends React.Props<any> {
   onSubmit: (spec: JSON) => void;
@@ -47,7 +48,7 @@ export class SpecDialog extends 
React.Component<SpecDialogProps, SpecDialogState
     super(props);
     this.state = {
       spec: ""
-    }
+    };
   }
 
   private postSpec(): void {
@@ -72,11 +73,11 @@ export class SpecDialog extends 
React.Component<SpecDialogProps, SpecDialogState
         mode="json"
         theme="solarized_dark"
         className={"post-spec-dialog-textarea"}
-        onChange={ (e) => {this.setState({ spec: e })} }
+        onChange={(e) => { this.setState({ spec: e }); }}
         fontSize={12}
         showPrintMargin={false}
-        showGutter={true}
-        highlightActiveLine={true}
+        showGutter
+        highlightActiveLine
         value={spec}
         width={"100%"}
         setOptions={{
@@ -84,7 +85,7 @@ export class SpecDialog extends 
React.Component<SpecDialogProps, SpecDialogState
           enableLiveAutocompletion: true,
           showLineNumbers: true,
           enableSnippets: true,
-          tabSize: 2,
+          tabSize: 2
         }}
       />
       <div className={Classes.DIALOG_FOOTER}>
diff --git a/web-console/src/entry.ts b/web-console/src/entry.ts
index ae2892c..9597d70 100644
--- a/web-console/src/entry.ts
+++ b/web-console/src/entry.ts
@@ -19,13 +19,14 @@
 import 'es6-shim/es6-shim';
 import 'es7-shim'; // Webpack with automatically pick browser.js which does 
the shim()
 import * as React from 'react';
-(React as any).PropTypes = require('prop-types'); // Trick blueprint 1.0.1 
into accepting React 16 as React 15.
 import * as ReactDOM from 'react-dom';
-import "./singletons/react-table-defaults";
-import "./entry.scss";
 
+import "./bootstrap/a-shim-for-react-props";
+import "./bootstrap/react-table-defaults";
 import { ConsoleApplication } from './console-application';
 
+import "./entry.scss";
+
 const container = document.getElementsByClassName('app-container')[0];
 if (!container) throw new Error('container not found');
 
diff --git a/web-console/src/singletons/toaster.ts 
b/web-console/src/singletons/toaster.ts
index 3f4998e..4fc529c 100644
--- a/web-console/src/singletons/toaster.ts
+++ b/web-console/src/singletons/toaster.ts
@@ -20,5 +20,5 @@ import { Position, Toaster } from "@blueprintjs/core";
 
 export const AppToaster = Toaster.create({
   className: "recipe-toaster",
-  position: Position.TOP,
+  position: Position.TOP
 });
diff --git a/web-console/src/utils/general.tsx 
b/web-console/src/utils/general.tsx
index a40ebad..7fce279 100644
--- a/web-console/src/utils/general.tsx
+++ b/web-console/src/utils/general.tsx
@@ -17,14 +17,14 @@
  */
 
 import { Button, InputGroup, Intent } from '@blueprintjs/core';
-import { IconNames, HTMLSelect } from "../components/filler";
 import * as numeral from "numeral";
 import * as React from 'react';
 import { Filter, FilterRender } from 'react-table';
 
+import { HTMLSelect, IconNames } from "../components/filler";
 
 export function addFilter(filters: Filter[], id: string, value: string): 
Filter[] {
-  let currentFilter = filters.find(f => f.id === id);
+  const currentFilter = filters.find(f => f.id === id);
   if (currentFilter) {
     filters = filters.filter(f => f.id !== id);
     if (currentFilter.value !== value) {
@@ -36,7 +36,7 @@ export function addFilter(filters: Filter[], id: string, 
value: string): Filter[
   return filters;
 }
 
-export function makeTextFilter(placeholder: string = ''): FilterRender {
+export function makeTextFilter(placeholder = ''): FilterRender {
   return ({ filter, onChange, key }) => {
     const filterValue = filter ? filter.value : '';
     return <InputGroup
@@ -45,8 +45,8 @@ export function makeTextFilter(placeholder: string = ''): 
FilterRender {
       value={filterValue}
       rightElement={filterValue ? <Button iconName={IconNames.CROSS} 
className="pt-minimal" onClick={() => onChange('')} /> : undefined}
       placeholder={placeholder}
-    />
-  }
+    />;
+  };
 }
 
 export function makeBooleanFilter(): FilterRender {
@@ -57,13 +57,13 @@ export function makeBooleanFilter(): FilterRender {
       style={{ width: '100%' }}
       onChange={(event: any) => onChange(event.target.value)}
       value={filterValue || "all"}
-      fill={true}
+      fill
     >
       <option value="all">Show all</option>
       <option value="true">true</option>
       <option value="false">false</option>
     </HTMLSelect>;
-  }
+  };
 }
 
 // ----------------------------
diff --git a/web-console/src/utils/query-manager.tsx 
b/web-console/src/utils/query-manager.tsx
index 38ef3ef..22fc3f7 100644
--- a/web-console/src/utils/query-manager.tsx
+++ b/web-console/src/utils/query-manager.tsx
@@ -74,7 +74,7 @@ export class QueryManager<Q, R> {
   private run() {
     this.lastQuery = this.nextQuery;
     this.currentQueryId++;
-    let myQueryId = this.currentQueryId;
+    const myQueryId = this.currentQueryId;
 
     this.actuallyLoading = true;
     this.processQuery(this.lastQuery)
@@ -95,9 +95,9 @@ export class QueryManager<Q, R> {
             result: null,
             loading: false,
             error: e.message
-          })
+          });
         }
-      )
+      );
   }
 
   private trigger() {
diff --git a/web-console/src/utils/rune-decoder.tsx 
b/web-console/src/utils/rune-decoder.tsx
index c3fe5a1..3863812 100644
--- a/web-console/src/utils/rune-decoder.tsx
+++ b/web-console/src/utils/rune-decoder.tsx
@@ -16,7 +16,6 @@
  * limitations under the License.
  */
 
-
 export interface HeaderRows {
   header: string[];
   rows: any[][];
@@ -86,7 +85,11 @@ export function decodeRune(runeQuery: any, runeResult: 
any[]): HeaderRows {
         throw new Error(`Unsupported query type in treatQueryTypeAs: 
'${treatQueryTypeAs}'`);
       }
     } else {
-      throw new Error(`Unsupported query type '${queryType}'. Supported query 
types are: '${SUPPORTED_QUERY_TYPES.join("', '")}'. If this is a custom query 
you can parse the result as a known query type by setting 'treatQueryTypeAs' in 
the context to one of the supported types.`);
+      throw new Error([
+        `Unsupported query type '${queryType}'.`,
+        `Supported query types are: '${SUPPORTED_QUERY_TYPES.join("', '")}'.`,
+        `If this is a custom query you can parse the result as a known query 
type by setting 'treatQueryTypeAs' in the context to one of the supported 
types.`
+      ].join(' '));
     }
   }
 
diff --git a/web-console/src/views/datasource-view.tsx 
b/web-console/src/views/datasource-view.tsx
index aacaace..1deae85 100644
--- a/web-console/src/views/datasource-view.tsx
+++ b/web-console/src/views/datasource-view.tsx
@@ -16,25 +16,26 @@
  * limitations under the License.
  */
 
+import { Button, Intent, Switch } from "@blueprintjs/core";
 import axios from 'axios';
-import * as React from 'react';
 import * as classNames from 'classnames';
+import * as React from 'react';
 import ReactTable from "react-table";
 import { Filter } from "react-table";
-import { Button, Intent, Switch } from "@blueprintjs/core";
+
 import { IconNames } from "../components/filler";
-import { AppToaster } from '../singletons/toaster';
 import { RuleEditor } from '../components/rule-editor';
 import { AsyncActionDialog } from '../dialogs/async-action-dialog';
 import { RetentionDialog } from '../dialogs/retention-dialog';
+import { AppToaster } from '../singletons/toaster';
 import {
   addFilter,
-  formatNumber,
-  formatBytes,
   countBy,
+  formatBytes,
+  formatNumber,
+  getDruidErrorMessage,
   lookupBy,
-  QueryManager,
-  pluralIfNeeded, queryDruidSql, getDruidErrorMessage
+  pluralIfNeeded, queryDruidSql, QueryManager
 } from "../utils";
 
 import "./datasource-view.scss";
@@ -54,7 +55,7 @@ export interface DatasourcesViewState {
   datasourcesLoading: boolean;
   datasources: Datasource[] | null;
   tiers: string[];
-  defaultRules: any[]
+  defaultRules: any[];
   datasourcesError: string | null;
   datasourcesFilter: Filter[];
 
@@ -73,7 +74,7 @@ export class DatasourcesView extends 
React.Component<DatasourcesViewProps, Datas
   static formatRules(rules: any[]): string {
     if (rules.length === 0) {
       return 'No rules';
-    } if (rules.length <= 2) {
+    } else if (rules.length <= 2) {
       return rules.map(RuleEditor.ruleToString).join(', ');
     } else {
       return `${RuleEditor.ruleToString(rules[0])} +${rules.length - 1} more 
rules`;
@@ -161,7 +162,7 @@ GROUP BY 1`);
     return <AsyncActionDialog
       action={
         dropDataDatasource ? async () => {
-          const resp = await 
axios.delete(`/druid/coordinator/v1/datasources/${dropDataDatasource}`, {})
+          const resp = await 
axios.delete(`/druid/coordinator/v1/datasources/${dropDataDatasource}`, {});
           return resp.data;
         } : null
       }
@@ -186,7 +187,7 @@ GROUP BY 1`);
     return <AsyncActionDialog
       action={
         enableDatasource ? async () => {
-          const resp = await 
axios.post(`/druid/coordinator/v1/datasources/${enableDatasource}`, {})
+          const resp = await 
axios.post(`/druid/coordinator/v1/datasources/${enableDatasource}`, {});
           return resp.data;
         } : null
       }
@@ -291,7 +292,7 @@ GROUP BY 1`);
 
     let data = datasources || [];
     if (!showDisabled) {
-      data = data.filter(d => !d.disabled)
+      data = data.filter(d => !d.disabled);
     }
 
     return <>
@@ -299,7 +300,7 @@ GROUP BY 1`);
         data={data}
         loading={datasourcesLoading}
         noDataText={!datasourcesLoading && datasources && !datasources.length 
? 'No datasources' : (datasourcesError || '')}
-        filterable={true}
+        filterable
         filtered={datasourcesFilter}
         onFilteredChange={(filtered, column) => {
           this.setState({ datasourcesFilter: filtered });
@@ -311,7 +312,7 @@ GROUP BY 1`);
             width: 150,
             Cell: row => {
               const value = row.value;
-              return <a onClick={() => { this.setState({ datasourcesFilter: 
addFilter(datasourcesFilter, 'datasource', value) }) }}>{value}</a>
+              return <a onClick={() => { this.setState({ datasourcesFilter: 
addFilter(datasourcesFilter, 'datasource', value) }); }}>{value}</a>;
             }
           },
           {
@@ -415,11 +416,11 @@ GROUP BY 1`);
                 return <div>
                   <a onClick={() => this.setState({ enableDatasource: 
datasource })}>Enable</a>&nbsp;&nbsp;&nbsp;
                   <a onClick={() => this.setState({ killDatasource: datasource 
})}>Permanently delete</a>
-                </div>
+                </div>;
               } else {
                 return <div>
                   <a onClick={() => this.setState({ dropDataDatasource: 
datasource })}>Drop data</a>
-                </div>
+                </div>;
               }
             }
           }
@@ -458,7 +459,6 @@ GROUP BY 1`);
         />
       </div>
       {this.renderDatasourceTable()}
-    </div>
+    </div>;
   }
 }
-
diff --git a/web-console/src/views/home-view.tsx 
b/web-console/src/views/home-view.tsx
index 83df542..b435bba 100644
--- a/web-console/src/views/home-view.tsx
+++ b/web-console/src/views/home-view.tsx
@@ -17,10 +17,12 @@
  */
 
 import axios from 'axios';
-import * as React from 'react';
 import * as classNames from 'classnames';
-import { H5, Card, Icon, IconNames } from "../components/filler";
-import { QueryManager, pluralIfNeeded, queryDruidSql, getHeadProp } from 
'../utils';
+import * as React from 'react';
+
+import { Card, H5, Icon, IconNames } from "../components/filler";
+import { getHeadProp, pluralIfNeeded, queryDruidSql, QueryManager } from 
'../utils';
+
 import './home-view.scss';
 
 export interface CardOptions {
@@ -164,14 +166,14 @@ export class HomeView extends 
React.Component<HomeViewProps, HomeViewState> {
     this.taskQueryManager = new QueryManager({
       processQuery: async (query) => {
         const taskCountsFromSql = await queryDruidSql({ query });
-        let taskCounts = {
+        const taskCounts = {
           successTaskCount: 0,
           failedTaskCount: 0,
           runningTaskCount: 0,
           waitingTaskCount: 0,
           pendingTaskCount: 0
         };
-        for (let dataStatus of taskCountsFromSql) {
+        for (const dataStatus of taskCountsFromSql) {
           if (dataStatus.status === "SUCCESS") {
             taskCounts.successTaskCount = dataStatus.count;
           } else if (dataStatus.status === "FAILED") {
@@ -254,7 +256,7 @@ GROUP BY 1`);
 
   renderCard(cardOptions: CardOptions): JSX.Element {
     return <a href={cardOptions.href} target={cardOptions.href[0] === '/' ? 
'_blank' : undefined}>
-      <Card interactive={true}>
+      <Card interactive>
         <H5><Icon color="#bfccd5" 
icon={cardOptions.icon}/>&nbsp;{cardOptions.title}</H5>
         {cardOptions.loading ? <p>Loading...</p> : (cardOptions.error ? 
`Error: ${cardOptions.error}` : cardOptions.content)}
       </Card>
@@ -298,11 +300,11 @@ GROUP BY 1`);
         title: "Tasks",
         loading: state.taskCountLoading,
         content: <>
-          { Boolean(state.runningTaskCount) && 
<p>{pluralIfNeeded(state.runningTaskCount, 'running task')}</p> }
-          { Boolean(state.pendingTaskCount) && 
<p>{pluralIfNeeded(state.pendingTaskCount, 'pending task')}</p> }
-          { Boolean(state.successTaskCount) && 
<p>{pluralIfNeeded(state.successTaskCount, 'successful task')}</p> }
-          { Boolean(state.waitingTaskCount) && 
<p>{pluralIfNeeded(state.waitingTaskCount, 'waiting task')}</p> }
-          { Boolean(state.failedTaskCount) && 
<p>{pluralIfNeeded(state.failedTaskCount, 'failed task')}</p> }
+          {Boolean(state.runningTaskCount) && 
<p>{pluralIfNeeded(state.runningTaskCount, 'running task')}</p>}
+          {Boolean(state.pendingTaskCount) && 
<p>{pluralIfNeeded(state.pendingTaskCount, 'pending task')}</p>}
+          {Boolean(state.successTaskCount) && 
<p>{pluralIfNeeded(state.successTaskCount, 'successful task')}</p>}
+          {Boolean(state.waitingTaskCount) && 
<p>{pluralIfNeeded(state.waitingTaskCount, 'waiting task')}</p>}
+          {Boolean(state.failedTaskCount) && 
<p>{pluralIfNeeded(state.failedTaskCount, 'failed task')}</p>}
           { !(state.runningTaskCount + state.pendingTaskCount + 
state.successTaskCount + state.waitingTaskCount + state.failedTaskCount) &&
             <p>There are no tasks</p>
           }
@@ -321,7 +323,6 @@ GROUP BY 1`);
         </>,
         error: state.dataServerCountError
       })}
-    </div>
+    </div>;
   }
 }
-
diff --git a/web-console/src/views/lookups-view.tsx 
b/web-console/src/views/lookups-view.tsx
index f7e9fe6..02a6d0e 100644
--- a/web-console/src/views/lookups-view.tsx
+++ b/web-console/src/views/lookups-view.tsx
@@ -16,15 +16,17 @@
  * limitations under the License.
  */
 
+import { Button, Intent } from "@blueprintjs/core";
 import axios from 'axios';
-import * as React from 'react';
 import * as classNames from 'classnames';
+import * as React from 'react';
 import ReactTable from "react-table";
 import { Filter } from "react-table";
-import { Button, Intent } from "@blueprintjs/core";
-import {getDruidErrorMessage, QueryManager} from "../utils";
-import {LookupEditDialog} from "../dialogs/lookup-edit-dialog";
+
+import { LookupEditDialog } from "../dialogs/lookup-edit-dialog";
 import { AppToaster } from "../singletons/toaster";
+import { getDruidErrorMessage, QueryManager } from "../utils";
+
 import "./lookups-view.scss";
 
 export interface LookupsViewProps extends React.Props<any> {
@@ -32,16 +34,16 @@ export interface LookupsViewProps extends React.Props<any> {
 }
 
 export interface LookupsViewState {
-  lookups: {}[],
-  loadingLookups: boolean,
-  lookupsError: string | null,
-  lookupEditDialogOpen: boolean,
-  lookupEditName: string,
-  lookupEditTier: string,
-  lookupEditVersion: string,
-  lookupEditSpec: string,
-  isEdit: boolean,
-  allLookupTiers: string[]
+  lookups: {}[];
+  loadingLookups: boolean;
+  lookupsError: string | null;
+  lookupEditDialogOpen: boolean;
+  lookupEditName: string;
+  lookupEditTier: string;
+  lookupEditVersion: string;
+  lookupEditSpec: string;
+  isEdit: boolean;
+  allLookupTiers: string[];
 }
 
 export class LookupsView extends React.Component<LookupsViewProps, 
LookupsViewState> {
@@ -70,15 +72,15 @@ export class LookupsView extends 
React.Component<LookupsViewProps, LookupsViewSt
         const tiersResp = await axios.get('/druid/coordinator/v1/tiers');
         const tiers = tiersResp.data;
 
-        let lookupEntries: {}[] = [];
+        const lookupEntries: {}[] = [];
         const lookupResp = await 
axios.get("/druid/coordinator/v1/lookups/config/all");
         const lookupData = lookupResp.data;
         Object.keys(lookupData).map((tier: string) => {
           const lookupIds = lookupData[tier];
           Object.keys(lookupIds).map((id: string) => {
-            lookupEntries.push({tier: tier, id: id, 
version:lookupData[tier][id].version, spec: 
lookupData[tier][id].lookupExtractorFactory},);
-          })
-        })
+            lookupEntries.push({tier, id, version: 
lookupData[tier][id].version, spec: 
lookupData[tier][id].lookupExtractorFactory});
+          });
+        });
         return {
           lookupEntries,
           tiers
@@ -123,11 +125,11 @@ export class LookupsView extends 
React.Component<LookupsViewProps, LookupsViewSt
           intent: Intent.DANGER,
           message: getDruidErrorMessage(e)
         }
-      )
+      );
     }
   }
 
-  private async openLookupEditDialog(tier:string, id: string) {
+  private async openLookupEditDialog(tier: string, id: string) {
     const { lookups, allLookupTiers } = this.state;
     const target: any = lookups.find((lookupEntry: any) => {
       return lookupEntry.tier === tier && lookupEntry.id === id;
@@ -156,7 +158,7 @@ export class LookupsView extends 
React.Component<LookupsViewProps, LookupsViewSt
   private changeLookup(field: string, value: string) {
     this.setState({
       [field]: value
-    } as any)
+    } as any);
   }
 
   private async submitLookupEdit() {
@@ -184,20 +186,20 @@ export class LookupsView extends 
React.Component<LookupsViewProps, LookupsViewSt
       await axios.post(endpoint, dataJSON);
       this.setState({
         lookupEditDialogOpen: false
-      })
+      });
       this.lookupsGetQueryManager.rerunLastQuery();
-    } catch(e) {
+    } catch (e) {
       AppToaster.show(
         {
           iconName: 'error',
           intent: Intent.DANGER,
           message: getDruidErrorMessage(e)
         }
-      )
+      );
     }
   }
 
-  private deleteLookup(tier:string, name: string): void {
+  private deleteLookup(tier: string, name: string): void {
     const url = `/druid/coordinator/v1/lookups/config/${tier}/${name}`;
     this.lookupDeleteQueryManager.runQuery(url);
   }
@@ -211,52 +213,52 @@ export class LookupsView extends 
React.Component<LookupsViewProps, LookupsViewSt
           text="Initialize Lookup"
           onClick={() => this.initializeLookup()}
         />
-      </div>
+      </div>;
     }
     return <>
       <ReactTable
         data={lookups}
         loading={loadingLookups}
         noDataText={!loadingLookups && lookups && !lookups.length ? 'No 
lookups' : (lookupsError || '')}
-        filterable={true}
+        filterable
         columns={[
           {
             Header: "Lookup Name",
             id: "lookup_name",
             accessor: (row: any) => row.id,
-            filterable: true,
+            filterable: true
           },
           {
             Header: "Tier",
             id: "tier",
             accessor: (row: any) => row.tier,
-            filterable: true,
+            filterable: true
           },
           {
             Header: "Type",
             id: "type",
             accessor: (row: any) => row.spec.type,
-            filterable: true,
+            filterable: true
           },
           {
             Header: "Version",
             id: "version",
             accessor: (row: any) => row.version,
-            filterable: true,
+            filterable: true
           },
           {
             Header: "Config",
             id: "config",
-            accessor: row => {return {id: row.id, tier: row.tier};},
+            accessor: row => ({id: row.id, tier: row.tier}),
             filterable: false,
             Cell: (row: any) => {
               const lookupId = row.value.id;
               const lookupTier = row.value.tier;
               return <div>
-                <a onClick={() => 
this.openLookupEditDialog(lookupTier,lookupId)}>Edit</a>
+                <a onClick={() => this.openLookupEditDialog(lookupTier, 
lookupId)}>Edit</a>
                 &nbsp;&nbsp;&nbsp;
-                <a onClick={() => 
this.deleteLookup(lookupTier,lookupId)}>Delete</a>
-              </div>
+                <a onClick={() => this.deleteLookup(lookupTier, 
lookupId)}>Delete</a>
+              </div>;
             }
           }
         ]}
@@ -266,21 +268,21 @@ export class LookupsView extends 
React.Component<LookupsViewProps, LookupsViewSt
     </>;
   }
 
-  renderLookupEditDialog () {
-    const { lookupEditDialogOpen, allLookupTiers, lookupEditSpec, 
lookupEditTier, lookupEditName, lookupEditVersion, isEdit } = this.state
+  renderLookupEditDialog() {
+    const { lookupEditDialogOpen, allLookupTiers, lookupEditSpec, 
lookupEditTier, lookupEditName, lookupEditVersion, isEdit } = this.state;
 
     return <LookupEditDialog
       isOpen={lookupEditDialogOpen}
       onClose={() => this.setState({ lookupEditDialogOpen: false })}
       onSubmit={() => this.submitLookupEdit()}
       onChange={(field: string, value: string) => this.changeLookup(field, 
value)}
-      lookupSpec= {lookupEditSpec}
+      lookupSpec={lookupEditSpec}
       lookupName={lookupEditName}
       lookupTier={lookupEditTier}
-      lookupVersion = {lookupEditVersion}
+      lookupVersion={lookupEditVersion}
       isEdit={isEdit}
       allLookupTiers={allLookupTiers}
-    />
+    />;
   }
 
   render() {
@@ -301,6 +303,6 @@ export class LookupsView extends 
React.Component<LookupsViewProps, LookupsViewSt
       </div>
       {this.renderLookupsTable()}
       {this.renderLookupEditDialog()}
-    </div>
+    </div>;
   }
-}
\ No newline at end of file
+}
diff --git a/web-console/src/views/segments-view.tsx 
b/web-console/src/views/segments-view.tsx
index 920f61a..8320f05 100644
--- a/web-console/src/views/segments-view.tsx
+++ b/web-console/src/views/segments-view.tsx
@@ -16,22 +16,24 @@
  * limitations under the License.
  */
 
+import { Button } from "@blueprintjs/core";
 import axios from 'axios';
-import * as React from 'react';
 import * as classNames from 'classnames';
+import * as React from 'react';
 import ReactTable from "react-table";
 import { Filter } from "react-table";
-import { Button } from "@blueprintjs/core";
+
 import { H5, IconNames } from "../components/filler";
 import {
   addFilter,
-  makeBooleanFilter,
-  QueryManager,
   formatBytes,
   formatNumber,
+  makeBooleanFilter,
   parseList,
-  queryDruidSql
+  queryDruidSql,
+  QueryManager
 } from "../utils";
+
 import "./segments-view.scss";
 
 export interface SegmentsViewProps extends React.Props<any> {
@@ -88,7 +90,7 @@ export class SegmentsView extends 
React.Component<SegmentsViewProps, SegmentsVie
           segmentsError: error
         });
       }
-    })
+    });
   }
 
   componentWillUnmount(): void {
@@ -99,7 +101,7 @@ export class SegmentsView extends 
React.Component<SegmentsViewProps, SegmentsVie
     const { page, pageSize, filtered, sorted } = state;
     const totalQuerySize = (page + 1) * pageSize;
 
-    let queryParts = [
+    const queryParts = [
       `SELECT "segment_id", "datasource", "start", "end", "size", "version", 
"partition_num", "num_replicas", "num_rows", "is_published", "is_available", 
"is_realtime", "payload"`,
       `FROM sys.segments`
     ];
@@ -114,7 +116,7 @@ export class SegmentsView extends 
React.Component<SegmentsViewProps, SegmentsVie
     }).filter(Boolean);
 
     if (whereParts.length) {
-      queryParts.push('WHERE ' + whereParts.join(' AND '))
+      queryParts.push('WHERE ' + whereParts.join(' AND '));
     }
 
     if (sorted.length) {
@@ -160,7 +162,7 @@ export class SegmentsView extends 
React.Component<SegmentsViewProps, SegmentsVie
           accessor: "datasource",
           Cell: row => {
             const value = row.value;
-            return <a onClick={() => { this.setState({ segmentFilter: 
addFilter(segmentFilter, 'datasource', value) }) }}>{value}</a>
+            return <a onClick={() => { this.setState({ segmentFilter: 
addFilter(segmentFilter, 'datasource', value) }); }}>{value}</a>;
           }
         },
         {
@@ -170,7 +172,7 @@ export class SegmentsView extends 
React.Component<SegmentsViewProps, SegmentsVie
           defaultSortDesc: true,
           Cell: row => {
             const value = row.value;
-            return <a onClick={() => { this.setState({ segmentFilter: 
addFilter(segmentFilter, 'start', value) }) }}>{value}</a>
+            return <a onClick={() => { this.setState({ segmentFilter: 
addFilter(segmentFilter, 'start', value) }); }}>{value}</a>;
           }
         },
         {
@@ -180,14 +182,14 @@ export class SegmentsView extends 
React.Component<SegmentsViewProps, SegmentsVie
           width: 120,
           Cell: row => {
             const value = row.value;
-            return <a onClick={() => { this.setState({ segmentFilter: 
addFilter(segmentFilter, 'end', value) }) }}>{value}</a>
+            return <a onClick={() => { this.setState({ segmentFilter: 
addFilter(segmentFilter, 'end', value) }); }}>{value}</a>;
           }
         },
         {
           Header: "Version",
           accessor: "version",
           defaultSortDesc: true,
-          width: 120,
+          width: 120
         },
         {
           Header: "Partition",
@@ -272,6 +274,6 @@ export class SegmentsView extends 
React.Component<SegmentsViewProps, SegmentsVie
         />
       </div>
       {this.renderSegmentsTable()}
-    </div>
+    </div>;
   }
 }
diff --git a/web-console/src/views/servers-view.tsx 
b/web-console/src/views/servers-view.tsx
index 89ac5c4..e8598be 100644
--- a/web-console/src/views/servers-view.tsx
+++ b/web-console/src/views/servers-view.tsx
@@ -16,19 +16,21 @@
  * limitations under the License.
  */
 
+import { Button, Switch } from "@blueprintjs/core";
 import axios from 'axios';
-import * as React from 'react';
 import * as classNames from 'classnames';
+import { sum } from "d3-array";
+import * as React from 'react';
 import ReactTable from "react-table";
 import { Filter } from "react-table";
-import { sum } from "d3-array";
-import { Button, Switch } from "@blueprintjs/core";
+
 import { IconNames } from '../components/filler';
-import { addFilter, formatBytes, formatBytesCompact, QueryManager, 
queryDruidSql } from "../utils";
+import { addFilter, formatBytes, formatBytesCompact, queryDruidSql, 
QueryManager } from "../utils";
+
 import "./servers-view.scss";
 
 function formatQueues(segmentsToLoad: number, segmentsToLoadSize: number, 
segmentsToDrop: number, segmentsToDropSize: number): string {
-  let queueParts: string[] = [];
+  const queueParts: string[] = [];
   if (segmentsToLoad) {
     queueParts.push(`${segmentsToLoad} segments to load 
(${formatBytesCompact(segmentsToLoadSize)})`);
   }
@@ -73,7 +75,7 @@ export class ServersView extends 
React.Component<ServersViewProps, ServersViewSt
       middleManagersLoading: true,
       middleManagers: null,
       middleManagersError: null,
-      middleManagerFilter: props.middleManager ? [{ id: 'host', value: 
props.middleManager }] : [],
+      middleManagerFilter: props.middleManager ? [{ id: 'host', value: 
props.middleManager }] : []
     };
   }
 
@@ -82,7 +84,7 @@ export class ServersView extends 
React.Component<ServersViewProps, ServersViewSt
       processQuery: async (query: string) => {
         const servers = await queryDruidSql({ query });
 
-        let loadQueueResponse = await 
axios.get("/druid/coordinator/v1/loadqueue?simple");
+        const loadQueueResponse = await 
axios.get("/druid/coordinator/v1/loadqueue?simple");
         const loadQueues = loadQueueResponse.data;
 
         return servers.map((s: any) => {
@@ -143,7 +145,7 @@ WHERE "server_type" = 'historical'`);
       data={servers || []}
       loading={serversLoading}
       noDataText={!serversLoading && servers && !servers.length ? 'No 
historicals' : (serversError || '')}
-      filterable={true}
+      filterable
       filtered={serverFilter}
       onFilteredChange={(filtered, column) => {
         this.setState({ serverFilter: filtered });
@@ -161,7 +163,7 @@ WHERE "server_type" = 'historical'`);
           accessor: "tier",
           Cell: row => {
             const value = row.value;
-            return <a onClick={() => { this.setState({ serverFilter: 
addFilter(serverFilter, 'tier', value) }) }}>{value}</a>
+            return <a onClick={() => { this.setState({ serverFilter: 
addFilter(serverFilter, 'tier', value) }); }}>{value}</a>;
           }
         },
         {
@@ -234,7 +236,7 @@ WHERE "server_type" = 'historical'`);
             const segmentsToDrop = sum(originals, s => s.segmentsToDrop);
             const segmentsToDropSize = sum(originals, s => 
s.segmentsToDropSize);
             return formatQueues(segmentsToLoad, segmentsToLoadSize, 
segmentsToDrop, segmentsToDropSize);
-          },
+          }
         },
         {
           Header: "Host",
@@ -245,7 +247,7 @@ WHERE "server_type" = 'historical'`);
           Header: "Port",
           id: 'port',
           accessor: (row) => {
-            let ports: string[] = [];
+            const ports: string[] = [];
             if (row.plaintext_port !== -1) {
               ports.push(`${row.plaintext_port} (plain)`);
             }
@@ -255,7 +257,7 @@ WHERE "server_type" = 'historical'`);
             return ports.join(', ') || 'No port';
           },
           Aggregated: () => ''
-        },
+        }
       ]}
       defaultPageSize={10}
       className="-striped -highlight"
@@ -270,7 +272,7 @@ WHERE "server_type" = 'historical'`);
       data={middleManagers || []}
       loading={middleManagersLoading}
       noDataText={!middleManagersLoading && middleManagers && 
!middleManagers.length ? 'No MiddleManagers' : (middleManagersError || '')}
-      filterable={true}
+      filterable
       filtered={middleManagerFilter}
       onFilteredChange={(filtered, column) => {
         this.setState({ middleManagerFilter: filtered });
@@ -282,7 +284,7 @@ WHERE "server_type" = 'historical'`);
           accessor: (row) => row.worker.host,
           Cell: row => {
             const value = row.value;
-            return <a onClick={() => { this.setState({ middleManagerFilter: 
addFilter(middleManagerFilter, 'host', value) }) }}>{value}</a>
+            return <a onClick={() => { this.setState({ middleManagerFilter: 
addFilter(middleManagerFilter, 'host', value) }); }}>{value}</a>;
           }
         },
         {
@@ -297,7 +299,7 @@ WHERE "server_type" = 'historical'`);
           id: "availabilityGroups",
           width: 60,
           accessor: (row) => row.availabilityGroups.length,
-          filterable: false,
+          filterable: false
         },
         {
           Header: "Last completed task time",
@@ -317,7 +319,7 @@ WHERE "server_type" = 'historical'`);
             runningTasks.length ?
               <>
                 <span>Running tasks:</span>
-                <ul>{ runningTasks.map((t: string) => <li key={t}>{t}&nbsp;<a 
onClick={() => goToTask(t)}>&#x279A;</a></li>) }</ul>
+                <ul>{runningTasks.map((t: string) => <li key={t}>{t}&nbsp;<a 
onClick={() => goToTask(t)}>&#x279A;</a></li>)}</ul>
               </> :
               <span>No running tasks</span>
           }
@@ -362,7 +364,6 @@ WHERE "server_type" = 'historical'`);
         />
       </div>
       {this.renderMiddleManagerTable()}
-    </div>
+    </div>;
   }
 }
-
diff --git a/web-console/src/views/sql-view.tsx 
b/web-console/src/views/sql-view.tsx
index d424316..cde181e 100644
--- a/web-console/src/views/sql-view.tsx
+++ b/web-console/src/views/sql-view.tsx
@@ -17,19 +17,21 @@
  */
 
 import axios from 'axios';
-import * as React from 'react';
 import * as classNames from 'classnames';
-import ReactTable from "react-table";
 import * as Hjson from "hjson";
+import * as React from 'react';
+import ReactTable from "react-table";
+
 import { SqlControl } from '../components/sql-control';
 import {
-  QueryManager,
-  localStorageSet,
-  localStorageGet,
   decodeRune,
   HeaderRows,
-  queryDruidRune, queryDruidSql
+  localStorageGet,
+  localStorageSet,
+  queryDruidRune,
+  queryDruidSql, QueryManager
 } from '../utils';
+
 import "./sql-view.scss";
 
 export interface SqlViewProps extends React.Props<any> {
@@ -84,7 +86,7 @@ export class SqlView extends React.Component<SqlViewProps, 
SqlViewState> {
           error
         });
       }
-    })
+    });
   }
 
   componentWillUnmount(): void {
@@ -117,7 +119,6 @@ export class SqlView extends React.Component<SqlViewProps, 
SqlViewState> {
         }}
       />
       {this.renderResultTable()}
-    </div>
+    </div>;
   }
 }
-
diff --git a/web-console/src/views/tasks-view.tsx 
b/web-console/src/views/tasks-view.tsx
index 8219c88..0816c4c 100644
--- a/web-console/src/views/tasks-view.tsx
+++ b/web-console/src/views/tasks-view.tsx
@@ -16,17 +16,19 @@
  * limitations under the License.
  */
 
+import { Alert, Button, Intent } from "@blueprintjs/core";
 import axios from 'axios';
-import * as React from 'react';
 import * as classNames from 'classnames';
+import * as React from 'react';
 import ReactTable from "react-table";
 import { Filter } from "react-table";
-import { Button, Intent, Alert } from "@blueprintjs/core";
-import { ButtonGroup, Label, IconNames } from "../components/filler";
-import { addFilter, QueryManager, getDruidErrorMessage, countBy, 
formatDuration, queryDruidSql } from "../utils";
+
+import { ButtonGroup, IconNames, Label } from "../components/filler";
 import { AsyncActionDialog } from "../dialogs/async-action-dialog";
 import { SpecDialog } from "../dialogs/spec-dialog";
 import { AppToaster } from '../singletons/toaster';
+import { addFilter, countBy, formatDuration, getDruidErrorMessage, 
queryDruidSql, QueryManager } from "../utils";
+
 import "./tasks-view.scss";
 
 export interface TasksViewProps extends React.Props<any> {
@@ -102,7 +104,7 @@ export class TasksView extends 
React.Component<TasksViewProps, TasksViewState> {
   componentDidMount(): void {
     this.supervisorQueryManager = new QueryManager({
       processQuery: async (query: string) => {
-        const resp = await axios.get("/druid/indexer/v1/supervisor?full")
+        const resp = await axios.get("/druid/indexer/v1/supervisor?full");
         return resp.data;
       },
       onStateChange: ({ result, loading, error }) => {
@@ -299,7 +301,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
         data={supervisors || []}
         loading={supervisorsLoading}
         noDataText={!supervisorsLoading && supervisors && !supervisors.length 
? 'No supervisors' : (supervisorsError || '')}
-        filterable={true}
+        filterable
         columns={[
           {
             Header: "Datasource",
@@ -338,7 +340,9 @@ ORDER BY "rank" DESC, "created_time" DESC`);
               return <span>
                 <span
                   style={{ color: value === 'Suspended' ? '#d58512' : 
'#2167d5' }}
-                >&#x25cf;&nbsp;</span>
+                >
+                  &#x25cf;&nbsp;
+                </span>
                 {value}
               </span>;
             }
@@ -363,7 +367,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
                 {suspendResume}&nbsp;&nbsp;&nbsp;
                 <a onClick={() => this.setState({ resetSupervisorId: id 
})}>Reset</a>&nbsp;&nbsp;&nbsp;
                 <a onClick={() => this.setState({ terminateSupervisorId: id 
})}>Terminate</a>
-              </div>
+              </div>;
             }
           }
         ]}
@@ -413,7 +417,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
         data={tasks || []}
         loading={tasksLoading}
         noDataText={!tasksLoading && tasks && !tasks.length ? 'No tasks' : 
(tasksError || '')}
-        filterable={true}
+        filterable
         filtered={taskFilter}
         onFilteredChange={(filtered, column) => {
           this.setState({ taskFilter: filtered });
@@ -432,7 +436,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
             accessor: "type",
             Cell: row => {
               const value = row.value;
-              return <a onClick={() => { this.setState({ taskFilter: 
addFilter(taskFilter, 'type', value) }) }}>{value}</a>
+              return <a onClick={() => { this.setState({ taskFilter: 
addFilter(taskFilter, 'type', value) }); }}>{value}</a>;
             }
           },
           {
@@ -440,7 +444,7 @@ ORDER BY "rank" DESC, "created_time" DESC`);
             accessor: "datasource",
             Cell: row => {
               const value = row.value;
-              return <a onClick={() => { this.setState({ taskFilter: 
addFilter(taskFilter, 'datasource', value) }) }}>{value}</a>
+              return <a onClick={() => { this.setState({ taskFilter: 
addFilter(taskFilter, 'datasource', value) }); }}>{value}</a>;
             }
           },
           {
@@ -462,10 +466,12 @@ ORDER BY "rank" DESC, "created_time" DESC`);
               return <span>
                 <span
                   style={{ color: statusToColor(status) }}
-                >&#x25cf;&nbsp;</span>
-                { status }
-                { location && <a onClick={() => 
goToMiddleManager(locationHostname)} title={`Go to: 
${locationHostname}`}>&nbsp;&#x279A;</a> }
-                { errorMsg && <a onClick={() => this.setState({ alertErrorMsg: 
errorMsg })} title={errorMsg}>&nbsp;?</a> }
+                >
+                  &#x25cf;&nbsp;
+                </span>
+                {status}
+                {location && <a onClick={() => 
goToMiddleManager(locationHostname)} title={`Go to: 
${locationHostname}`}>&nbsp;&#x279A;</a>}
+                {errorMsg && <a onClick={() => this.setState({ alertErrorMsg: 
errorMsg })} title={errorMsg}>&nbsp;?</a>}
               </span>;
             },
             PivotValue: (opt) => {
@@ -503,8 +509,8 @@ ORDER BY "rank" DESC, "created_time" DESC`);
                 <a href={`/druid/indexer/v1/task/${id}/reports`} 
target="_blank">Reports</a>&nbsp;&nbsp;&nbsp;
                 <a href={`/druid/indexer/v1/task/${id}/log`} 
target="_blank">Log (all)</a>&nbsp;&nbsp;&nbsp;
                 <a href={`/druid/indexer/v1/task/${id}/log?offset=-8192`} 
target="_blank">Log (last 8kb)</a>&nbsp;&nbsp;&nbsp;
-                { (status === 'RUNNING' || status === 'WAITING' || status === 
'PENDING') && <a onClick={() => this.setState({ killTaskId: id })}>Kill</a> }
-              </div>
+                {(status === 'RUNNING' || status === 'WAITING' || status === 
'PENDING') && <a onClick={() => this.setState({ killTaskId: id })}>Kill</a>}
+              </div>;
             },
             Aggregated: row => ''
           }
@@ -583,7 +589,6 @@ ORDER BY "rank" DESC, "created_time" DESC`);
       >
         <p>{alertErrorMsg}</p>
       </Alert>
-    </div>
+    </div>;
   }
 }
-
diff --git a/web-console/tslint.json b/web-console/tslint.json
new file mode 100644
index 0000000..daed15b
--- /dev/null
+++ b/web-console/tslint.json
@@ -0,0 +1,125 @@
+{
+  "extends": [
+    "tslint:recommended",
+    "tslint-react"
+  ],
+  "rules": {
+    "align": true,
+    "array-type": [true, "array"],
+    "arrow-parens": false,
+    "ban": [false,
+      ["_", "extend"],
+      ["_", "isNull"],
+      ["_", "isDefined"]
+    ],
+    "comment-format": [false,
+      "check-space"
+    ],
+    "curly": [true, "ignore-same-line"],
+    "file-header": [true, "Licensed to the Apache Software Foundation 
\\(ASF\\).+"],
+    "forin": false,
+    "indent": [true, "spaces", 2],
+    "interface-name": [true, "never-prefix"],
+    "jsdoc-format": true,
+    "label-position": true,
+    "max-line-length": [true, 200],
+    "max-classes-per-file": false,
+    "member-access": false,
+    "member-ordering": [true,
+      {
+        "order": [
+          "static-field",
+          "static-method",
+          "instance-field",
+          "constructor",
+          "instance-method"
+        ]
+      }
+    ],
+    "no-any": false,
+    "no-arg": true,
+    "no-console": [true,
+      "debug",
+      "info",
+      "time",
+      "timeEnd",
+      "trace"
+    ],
+    "no-empty": [true, "allow-empty-catch"],
+    "no-empty-interface": false,
+    "no-eval": true,
+    "no-inferrable-types": true,
+    "no-null-keyword": false,
+    "no-parameter-properties": true,
+    "no-require-imports": false,
+    "no-shadowed-variable": false,
+    "no-string-literal": false,
+    "no-switch-case-fall-through": false,
+    "no-trailing-whitespace": true,
+    "no-unused-expression": false,
+    "no-use-before-declare": false,
+    "object-literal-sort-keys": false,
+    "one-line": [true,
+      "check-catch",
+      "check-else",
+      "check-finally",
+      "check-open-brace",
+      "check-whitespace"
+    ],
+    "ordered-imports": [
+      true,
+      {
+        "import-sources-order": "case-insensitive",
+        "grouped-imports": true,
+        "groups": [
+          { "name": "parent directories", "match": "^\\.\\.", "order": 20 },
+          { "name": "styles", "match": ".scss$", "order": 40 },
+          { "name": "current directory", "match": "^\\.", "order": 30 },
+          { "name": "libraries", "match": ".*", "order": 10 }
+        ]
+      }
+    ],
+    "prefer-const": [true, {"destructuring": "all"}],
+    "quotemark": [false, "double", "jsx-double", "avoid-escape"],
+    "semicolon": true,
+    "switch-default": false,
+    "trailing-comma": [true, {
+      "singleline": "never",
+      "multiline": "never"
+    }],
+    "triple-equals": [true, "allow-null-check"],
+    "typedef": false,
+    "typedef-whitespace": [true, {
+      "call-signature": "nospace",
+      "index-signature": "nospace",
+      "parameter": "nospace",
+      "property-declaration": "nospace",
+      "variable-declaration": "nospace"
+    }, {
+      "call-signature": "onespace",
+      "index-signature": "onespace",
+      "parameter": "onespace",
+      "property-declaration": "onespace",
+      "variable-declaration": "onespace"
+    }],
+    "variable-name": false,
+    "whitespace": [true,
+      "check-branch",
+      "check-decl",
+      "check-operator",
+      "check-module",
+      "check-separator",
+      "check-type",
+      "check-type-operator"
+    ],
+    "jsx-alignment": true,
+    "jsx-boolean-value": [true, "never"],
+    "jsx-curly-spacing": [true, "never"],
+    "jsx-no-lambda": false,
+    "jsx-no-multiline-js": false,
+    "jsx-no-string-ref": true,
+    "jsx-self-close": true,
+    "jsx-space-before-trailing-slash": false,
+    "jsx-wrap-multiline": false
+  }
+}
diff --git a/web-console/webpack.config.js b/web-console/webpack.config.js
index d9fad8b..cc248a2 100644
--- a/web-console/webpack.config.js
+++ b/web-console/webpack.config.js
@@ -52,6 +52,20 @@ module.exports = (env) => {
       rules: [
         {
           test: /\.tsx?$/,
+          enforce: 'pre',
+          use: [
+            {
+              loader: 'tslint-loader',
+              options: {
+                configFile: 'tslint.json',
+                emitErrors: true,
+                fix: false // Set this to true to auto fix errors
+              }
+            }
+          ]
+        },
+        {
+          test: /\.tsx?$/,
           use: 'ts-loader',
           exclude: /node_modules/
         },


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

Reply via email to