You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@druid.apache.org by cw...@apache.org on 2019/07/27 08:46:46 UTC

[incubator-druid] branch master updated: Web console: code quality improvements (null tidy up) (#8162)

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

cwylie 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 8bd0f8c  Web console: code quality improvements (null tidy up) (#8162)
8bd0f8c is described below

commit 8bd0f8c2ac65b1d0f1816721a044a53f3b271ee0
Author: Vadim Ogievetsky <va...@gmail.com>
AuthorDate: Sat Jul 27 01:46:37 2019 -0700

    Web console: code quality improvements (null tidy up) (#8162)
    
    * tidy up nulls
    
    * standardize more on undefined
    
    * updated licenses
    
    * update snapshot
    
    * do not do heavy handed rendering
    
    * add placeholder to SQL view
    
    * remove pointelss fragment
---
 licenses.yaml                                      |  20 ++++
 licenses/bin/lodash.compact.MIT                    |  23 +++++
 licenses/bin/lodash.escape.MIT                     |  47 +++++++++
 web-console/package-lock.json                      |  26 ++++-
 web-console/package.json                           |   4 +
 .../bootstrap/react-table-custom-pagination.tsx    |   2 +-
 web-console/src/bootstrap/react-table-defaults.tsx |   2 +-
 .../components/action-cell/action-cell.spec.tsx    |   2 +-
 .../src/components/action-cell/action-cell.tsx     |   2 +-
 .../src/components/action-icon/action-icon.tsx     |   2 +-
 .../components/array-input/array-input.spec.tsx    |   2 +-
 .../src/components/array-input/array-input.tsx     |   9 +-
 web-console/src/components/auto-form/auto-form.tsx |   4 +-
 .../components/center-message/center-message.tsx   |   2 +-
 .../clearable-input/clearable-input.spec.tsx       |   2 +-
 .../components/clearable-input/clearable-input.tsx |   2 +-
 .../src/components/external-link/external-link.tsx |   2 +-
 .../src/components/header-bar/header-bar.tsx       |   2 +-
 .../src/components/json-collapse/json-collapse.tsx |   2 +-
 .../src/components/json-input/json-input.tsx       |   2 +-
 web-console/src/components/loader/loader.tsx       |   2 +-
 .../src/components/menu-checkbox/menu-checkbox.tsx |   2 +-
 .../components/refresh-button/refresh-button.tsx   |   2 +-
 .../components/rule-editor/rule-editor.spec.tsx    |   4 +-
 .../src/components/rule-editor/rule-editor.tsx     |   2 +-
 web-console/src/components/show-json/show-json.tsx |   2 +-
 web-console/src/components/show-log/show-log.tsx   |   4 +-
 .../suggestible-input/suggestible-input.tsx        |   2 +-
 .../src/components/table-cell/table-cell.tsx       |   2 +-
 .../table-column-selector.tsx                      |   2 +-
 .../src/components/timed-button/timed-button.tsx   |   2 +-
 .../view-control-bar/view-control-bar.tsx          |   2 +-
 web-console/src/console-application.tsx            |   2 +-
 .../src/dialogs/about-dialog/about-dialog.spec.tsx |   2 +-
 .../src/dialogs/about-dialog/about-dialog.tsx      |   2 +-
 .../async-action-dialog.spec.tsx                   |   2 +-
 .../async-action-dialog/async-action-dialog.tsx    |   2 +-
 .../compaction-dialog/compaction-dialog.spec.tsx   |   6 +-
 .../compaction-dialog/compaction-dialog.tsx        |   7 +-
 .../coordinator-dynamic-config-dialog.spec.tsx     |   2 +-
 .../coordinator-dynamic-config-dialog.tsx          |   7 +-
 .../lookup-edit-dialog/lookup-edit-dialog.spec.tsx |   6 +-
 .../lookup-edit-dialog/lookup-edit-dialog.tsx      |   4 +-
 .../overload-dynamic-config-dialog.spec.tsx        |   2 +-
 .../overlord-dynamic-config-dialog.tsx             |   7 +-
 .../query-plan-dialog/query-plan-dialog.spec.tsx   |   6 +-
 .../query-plan-dialog/query-plan-dialog.tsx        |  16 +--
 .../retention-dialog/retention-dialog.spec.tsx     |   6 +-
 .../dialogs/retention-dialog/retention-dialog.tsx  |   2 +-
 .../segment-table-action-dialog.spec.tsx           |   2 +-
 .../segment-table-action-dialog.tsx                |   4 +-
 .../show-value-dialog/show-value-dialog.spec.tsx   |   2 +-
 .../show-value-dialog/show-value-dialog.tsx        |   2 +-
 .../dialogs/snitch-dialog/snitch-dialog.spec.tsx   |   2 +-
 .../src/dialogs/snitch-dialog/snitch-dialog.tsx    |   9 +-
 .../src/dialogs/spec-dialog/spec-dialog.spec.tsx   |   2 +-
 .../src/dialogs/spec-dialog/spec-dialog.tsx        |   2 +-
 .../supervisor-table-action-dialog.spec.tsx.snap   |  24 ++---
 .../supervisor-table-action-dialog.spec.tsx        |   2 +-
 .../supervisor-table-action-dialog.tsx             |  26 ++---
 .../table-action-dialog.spec.tsx                   |   2 +-
 .../table-action-dialog/table-action-dialog.tsx    |   2 +-
 .../task-table-action-dialog.spec.tsx              |   2 +-
 .../task-table-action-dialog.tsx                   |   2 +-
 web-console/src/utils/druid-query.ts               |  14 ++-
 web-console/src/utils/druid-type.ts                |   2 +-
 web-console/src/utils/general.tsx                  |   5 +-
 web-console/src/utils/ingestion-spec.tsx           |  81 ++++++++-------
 web-console/src/utils/local-storage-keys.tsx       |   6 +-
 web-console/src/utils/query-manager.tsx            |  14 ++-
 web-console/src/utils/query-state.ts               |   4 +-
 web-console/src/utils/rune-decoder.tsx             |  20 ++--
 web-console/src/utils/sampler.ts                   |   8 +-
 .../src/views/datasource-view/datasource-view.tsx  |  47 ++++-----
 .../__snapshots__/home-view.spec.tsx.snap          |  14 +--
 web-console/src/views/home-view/home-view.scss     |   4 +-
 web-console/src/views/home-view/home-view.tsx      |  81 +++++++--------
 .../filter-table/filter-table.spec.tsx             |   4 +-
 .../load-data-view/filter-table/filter-table.tsx   |   2 +-
 .../src/views/load-data-view/load-data-view.scss   |  21 ++--
 .../src/views/load-data-view/load-data-view.tsx    | 115 ++++++++++-----------
 .../parse-data-table/parse-data-table.spec.tsx     |   4 +-
 .../parse-data-table/parse-data-table.tsx          |   2 +-
 .../parse-time-table/parse-time-table.spec.tsx     |   2 +-
 .../parse-time-table/parse-time-table.tsx          |   4 +-
 .../schema-table/schema-table.spec.tsx             |   2 +-
 .../load-data-view/schema-table/schema-table.tsx   |  14 +--
 .../transform-table/transform-table.spec.tsx       |   2 +-
 .../transform-table/transform-table.tsx            |   2 +-
 .../__snapshots__/lookups-view.spec.tsx.snap       |   2 +-
 .../src/views/lookups-view/lookups-view.tsx        |  30 +++---
 .../__snapshots__/query-view.spec.tsx.snap         |   4 -
 .../query-view/column-tree/column-tree.spec.tsx    |   2 +-
 .../views/query-view/column-tree/column-tree.tsx   |  25 +++--
 .../query-extra-info/query-extra-info.spec.tsx     |   3 +-
 .../query-extra-info/query-extra-info.tsx          |   6 +-
 .../query-view/query-input/query-input.spec.tsx    |   7 +-
 .../views/query-view/query-input/query-input.tsx   |  43 ++++----
 .../query-view/query-output/query-output.spec.tsx  |   2 +-
 .../views/query-view/query-output/query-output.tsx |   6 +-
 web-console/src/views/query-view/query-view.tsx    |  52 ++++------
 .../query-view/run-button/run-button.spec.tsx      |   6 +-
 .../src/views/query-view/run-button/run-button.tsx |   4 +-
 .../src/views/segments-view/segments-view.tsx      |  60 +++++------
 .../src/views/servers-view/servers-view.tsx        |  26 ++---
 .../src/views/task-view/tasks-view.spec.tsx        |   6 +-
 web-console/src/views/task-view/tasks-view.tsx     |  80 ++++++--------
 web-console/tsconfig.json                          |   2 +-
 108 files changed, 619 insertions(+), 574 deletions(-)

diff --git a/licenses.yaml b/licenses.yaml
index 2e16f5f..372af99 100644
--- a/licenses.yaml
+++ b/licenses.yaml
@@ -2645,6 +2645,16 @@ license_file_path: licenses/bin/js-tokens.MIT
 
 ---
 
+name: "lodash.compact"
+license_category: binary
+module: web-console
+license_name: MIT License
+copyright: John-David Dalton
+version: 3.0.1
+license_file_path: licenses/bin/lodash.compact.MIT
+
+---
+
 name: "lodash.debounce"
 license_category: binary
 module: web-console
@@ -2655,6 +2665,16 @@ license_file_path: licenses/bin/lodash.debounce.MIT
 
 ---
 
+name: "lodash.escape"
+license_category: binary
+module: web-console
+license_name: MIT License
+copyright: John-David Dalton
+version: 4.0.1
+license_file_path: licenses/bin/lodash.escape.MIT
+
+---
+
 name: "lodash.get"
 license_category: binary
 module: web-console
diff --git a/licenses/bin/lodash.compact.MIT b/licenses/bin/lodash.compact.MIT
new file mode 100644
index 0000000..bcbe13d
--- /dev/null
+++ b/licenses/bin/lodash.compact.MIT
@@ -0,0 +1,23 @@
+The MIT License (MIT)
+
+Copyright 2012-2016 The Dojo Foundation <http://dojofoundation.org/>
+Based on Underscore.js, copyright 2009-2016 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/licenses/bin/lodash.escape.MIT b/licenses/bin/lodash.escape.MIT
new file mode 100644
index 0000000..e0c69d5
--- /dev/null
+++ b/licenses/bin/lodash.escape.MIT
@@ -0,0 +1,47 @@
+Copyright jQuery Foundation and other contributors <https://jquery.org/>
+
+Based on Underscore.js, copyright Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
+
+This software consists of voluntary contributions made by many
+individuals. For exact contribution history, see the revision history
+available at https://github.com/lodash/lodash
+
+The following license applies to all parts of this software except as
+documented below:
+
+====
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+====
+
+Copyright and related rights for sample code are waived via CC0. Sample
+code is defined as all source code displayed within the prose of the
+documentation.
+
+CC0: http://creativecommons.org/publicdomain/zero/1.0/
+
+====
+
+Files located in the node_modules and vendor directories are externally
+maintained libraries used by this software which have their own
+licenses; we recommend you read them, as their terms may differ from the
+terms above.
diff --git a/web-console/package-lock.json b/web-console/package-lock.json
index e7fe51f..c9c5c60 100644
--- a/web-console/package-lock.json
+++ b/web-console/package-lock.json
@@ -1282,6 +1282,15 @@
       "integrity": "sha512-0GJhzBdvsW2RUccNHOBkabI8HZVdOXmXbXhuKlDEd5Vv12P7oAVGfomGp3Ne21o5D/qu1WmthlNKFaoZJJeErA==",
       "dev": true
     },
+    "@types/lodash.compact": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/@types/lodash.compact/-/lodash.compact-3.0.6.tgz",
+      "integrity": "sha512-0pDKTX4alTyxH85Y5Al4YzS8oriqBQykADW6zLAHkZwNBMPXFIhdE2ctg0Z2GVcZsABxo5CI/J3vmHrFkdQBfA==",
+      "dev": true,
+      "requires": {
+        "@types/lodash": "*"
+      }
+    },
     "@types/lodash.debounce": {
       "version": "4.0.6",
       "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.6.tgz",
@@ -1291,6 +1300,15 @@
         "@types/lodash": "*"
       }
     },
+    "@types/lodash.escape": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/@types/lodash.escape/-/lodash.escape-4.0.6.tgz",
+      "integrity": "sha512-/CI97B3wf1R4oD4yotsGoX/5bobL/RC8olTQ16iunzdjufcdD8GyIvNDatg1IfzVKaFBZmxuY0+WQDpdaQHLzg==",
+      "dev": true,
+      "requires": {
+        "@types/lodash": "*"
+      }
+    },
     "@types/memoize-one": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/@types/memoize-one/-/memoize-one-4.1.1.tgz",
@@ -7469,6 +7487,11 @@
       "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=",
       "dev": true
     },
+    "lodash.compact": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/lodash.compact/-/lodash.compact-3.0.1.tgz",
+      "integrity": "sha1-VAzjg3dFl1gHRx4WtKK6IeclbKU="
+    },
     "lodash.debounce": {
       "version": "4.0.8",
       "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@@ -7477,8 +7500,7 @@
     "lodash.escape": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz",
-      "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=",
-      "dev": true
+      "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg="
     },
     "lodash.flattendeep": {
       "version": "4.4.0",
diff --git a/web-console/package.json b/web-console/package.json
index 7dc5408..8d7fbd9 100644
--- a/web-console/package.json
+++ b/web-console/package.json
@@ -60,7 +60,9 @@
     "file-saver": "^2.0.2",
     "has-own-prop": "^2.0.0",
     "hjson": "^3.1.2",
+    "lodash.compact": "^3.0.1",
     "lodash.debounce": "^4.0.8",
+    "lodash.escape": "^4.0.1",
     "memoize-one": "^5.0.5",
     "numeral": "^2.0.6",
     "react": "^16.8.6",
@@ -84,7 +86,9 @@
     "@types/file-saver": "^2.0.1",
     "@types/hjson": "^2.4.1",
     "@types/jest": "^24.0.15",
+    "@types/lodash.compact": "^3.0.6",
     "@types/lodash.debounce": "^4.0.6",
+    "@types/lodash.escape": "^4.0.6",
     "@types/memoize-one": "^4.1.1",
     "@types/node": "^12.6.2",
     "@types/numeral": "^0.0.25",
diff --git a/web-console/src/bootstrap/react-table-custom-pagination.tsx b/web-console/src/bootstrap/react-table-custom-pagination.tsx
index 5be343df..f45357d 100644
--- a/web-console/src/bootstrap/react-table-custom-pagination.tsx
+++ b/web-console/src/bootstrap/react-table-custom-pagination.tsx
@@ -83,7 +83,7 @@ export class ReactTableCustomPagination extends React.PureComponent<
     this.changePage(page === '' ? this.props.page : page);
   };
 
-  render() {
+  render(): JSX.Element {
     const {
       pages,
       page,
diff --git a/web-console/src/bootstrap/react-table-defaults.tsx b/web-console/src/bootstrap/react-table-defaults.tsx
index f0d2b2f..67b4121 100644
--- a/web-console/src/bootstrap/react-table-defaults.tsx
+++ b/web-console/src/bootstrap/react-table-defaults.tsx
@@ -27,7 +27,7 @@ import { ReactTableCustomPagination } from './react-table-custom-pagination';
 /* tslint:disable:max-classes-per-file */
 
 class NoData extends React.PureComponent {
-  render() {
+  render(): JSX.Element | null {
     const { children } = this.props;
     if (!children) return null;
     return <div className="rt-noData">{children}</div>;
diff --git a/web-console/src/components/action-cell/action-cell.spec.tsx b/web-console/src/components/action-cell/action-cell.spec.tsx
index 4f18bf8..f37d1ce 100644
--- a/web-console/src/components/action-cell/action-cell.spec.tsx
+++ b/web-console/src/components/action-cell/action-cell.spec.tsx
@@ -23,7 +23,7 @@ import { ActionCell } from './action-cell';
 
 describe('action cell', () => {
   it('matches snapshot', () => {
-    const actionCell = <ActionCell onDetail={() => null} actions={[]} />;
+    const actionCell = <ActionCell onDetail={() => {}} actions={[]} />;
     const { container } = render(actionCell);
     expect(container.firstChild).toMatchSnapshot();
   });
diff --git a/web-console/src/components/action-cell/action-cell.tsx b/web-console/src/components/action-cell/action-cell.tsx
index 32c2fa2..248c5ea 100644
--- a/web-console/src/components/action-cell/action-cell.tsx
+++ b/web-console/src/components/action-cell/action-cell.tsx
@@ -39,7 +39,7 @@ export class ActionCell extends React.PureComponent<ActionCellProps> {
     super(props, context);
   }
 
-  render() {
+  render(): JSX.Element {
     const { onDetail, actions } = this.props;
     const actionsMenu = actions ? basicActionsToMenu(actions) : null;
 
diff --git a/web-console/src/components/action-icon/action-icon.tsx b/web-console/src/components/action-icon/action-icon.tsx
index c3c7da2..d53192d 100644
--- a/web-console/src/components/action-icon/action-icon.tsx
+++ b/web-console/src/components/action-icon/action-icon.tsx
@@ -29,7 +29,7 @@ export interface ActionIconProps {
 }
 
 export class ActionIcon extends React.PureComponent<ActionIconProps> {
-  render() {
+  render(): JSX.Element {
     const { className, icon, onClick } = this.props;
 
     return <Icon className={classNames('action-icon', className)} icon={icon} onClick={onClick} />;
diff --git a/web-console/src/components/array-input/array-input.spec.tsx b/web-console/src/components/array-input/array-input.spec.tsx
index 8263983..bc21147 100644
--- a/web-console/src/components/array-input/array-input.spec.tsx
+++ b/web-console/src/components/array-input/array-input.spec.tsx
@@ -28,7 +28,7 @@ describe('array input', () => {
         values={['apple', 'banana', 'pear']}
         className={'test'}
         placeholder={'test'}
-        onChange={() => null}
+        onChange={() => {}}
       />
     );
 
diff --git a/web-console/src/components/array-input/array-input.tsx b/web-console/src/components/array-input/array-input.tsx
index 75aedb8..9d7a451 100644
--- a/web-console/src/components/array-input/array-input.tsx
+++ b/web-console/src/components/array-input/array-input.tsx
@@ -17,12 +17,13 @@
  */
 
 import { TextArea } from '@blueprintjs/core';
+import compact from 'lodash.compact';
 import React from 'react';
 
 export interface ArrayInputProps {
   className?: string;
   values: string[];
-  onChange: (newValues: string[]) => void;
+  onChange: (newValues: string[] | undefined) => void;
   placeholder?: string;
   large?: boolean;
   disabled?: boolean;
@@ -39,8 +40,8 @@ export class ArrayInput extends React.PureComponent<ArrayInputProps, { stringVal
   private handleChange = (e: any) => {
     const { onChange } = this.props;
     const stringValue = e.target.value;
-    const newValues = stringValue.split(',').map((v: string) => v.trim());
-    const newValuesFiltered = newValues.filter(Boolean);
+    const newValues: string[] = stringValue.split(',').map((v: string) => v.trim());
+    const newValuesFiltered = compact(newValues);
     this.setState({
       stringValue:
         newValues.length === newValuesFiltered.length ? newValues.join(', ') : stringValue,
@@ -48,7 +49,7 @@ export class ArrayInput extends React.PureComponent<ArrayInputProps, { stringVal
     if (onChange) onChange(stringValue === '' ? undefined : newValuesFiltered);
   };
 
-  render() {
+  render(): JSX.Element {
     const { className, placeholder, large, disabled } = this.props;
     const { stringValue } = this.state;
     return (
diff --git a/web-console/src/components/auto-form/auto-form.tsx b/web-console/src/components/auto-form/auto-form.tsx
index e7db357..ab2f9c8 100644
--- a/web-console/src/components/auto-form/auto-form.tsx
+++ b/web-console/src/components/auto-form/auto-form.tsx
@@ -42,7 +42,7 @@ export interface Field<T> {
 
 export interface AutoFormProps<T> {
   fields: Field<T>[];
-  model: T | null;
+  model: T | undefined;
   onChange: (newModel: T) => void;
   showCustom?: (model: T) => boolean;
   updateJSONValidity?: (jsonValidity: boolean) => void;
@@ -283,7 +283,7 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
     );
   }
 
-  render() {
+  render(): JSX.Element {
     const { fields, model, showCustom } = this.props;
     return (
       <div className="auto-form">
diff --git a/web-console/src/components/center-message/center-message.tsx b/web-console/src/components/center-message/center-message.tsx
index b84e0cf..6e88141 100644
--- a/web-console/src/components/center-message/center-message.tsx
+++ b/web-console/src/components/center-message/center-message.tsx
@@ -23,7 +23,7 @@ import './center-message.scss';
 export interface CenterMessageProps {}
 
 export class CenterMessage extends React.PureComponent<CenterMessageProps> {
-  render() {
+  render(): JSX.Element {
     return (
       <div className="center-message bp3-input">
         <div className="center-message-inner">{this.props.children}</div>
diff --git a/web-console/src/components/clearable-input/clearable-input.spec.tsx b/web-console/src/components/clearable-input/clearable-input.spec.tsx
index 9a20b53..3bea931 100644
--- a/web-console/src/components/clearable-input/clearable-input.spec.tsx
+++ b/web-console/src/components/clearable-input/clearable-input.spec.tsx
@@ -28,7 +28,7 @@ describe('clearable-input', () => {
         className={'testClassName'}
         value={'testValue'}
         placeholder={'testPlaceholder'}
-        onChange={() => null}
+        onChange={() => {}}
       >
         <div>Hello World</div>
       </ClearableInput>
diff --git a/web-console/src/components/clearable-input/clearable-input.tsx b/web-console/src/components/clearable-input/clearable-input.tsx
index 1376759..252bddc 100644
--- a/web-console/src/components/clearable-input/clearable-input.tsx
+++ b/web-console/src/components/clearable-input/clearable-input.tsx
@@ -29,7 +29,7 @@ export interface ClearableInputProps {
 }
 
 export class ClearableInput extends React.PureComponent<ClearableInputProps> {
-  render() {
+  render(): JSX.Element {
     const { className, value, onChange, placeholder } = this.props;
 
     return (
diff --git a/web-console/src/components/external-link/external-link.tsx b/web-console/src/components/external-link/external-link.tsx
index f4ca4b0..32428b0 100644
--- a/web-console/src/components/external-link/external-link.tsx
+++ b/web-console/src/components/external-link/external-link.tsx
@@ -23,7 +23,7 @@ export interface ExternalLinkProps {
 }
 
 export class ExternalLink extends React.PureComponent<ExternalLinkProps> {
-  render() {
+  render(): JSX.Element {
     const { href, children } = this.props;
 
     return (
diff --git a/web-console/src/components/header-bar/header-bar.tsx b/web-console/src/components/header-bar/header-bar.tsx
index 5d4bc15..45598dc 100644
--- a/web-console/src/components/header-bar/header-bar.tsx
+++ b/web-console/src/components/header-bar/header-bar.tsx
@@ -127,7 +127,7 @@ export class HeaderBar extends React.PureComponent<HeaderBarProps, HeaderBarStat
     );
   }
 
-  render() {
+  render(): JSX.Element {
     const { active, hideLegacy } = this.props;
     const {
       aboutDialogOpen,
diff --git a/web-console/src/components/json-collapse/json-collapse.tsx b/web-console/src/components/json-collapse/json-collapse.tsx
index 0441d78..0936392 100644
--- a/web-console/src/components/json-collapse/json-collapse.tsx
+++ b/web-console/src/components/json-collapse/json-collapse.tsx
@@ -36,7 +36,7 @@ export class JSONCollapse extends React.PureComponent<JSONCollapseProps, JSONCol
     };
   }
 
-  render() {
+  render(): JSX.Element {
     const { stringValue, buttonText } = this.props;
     const { isOpen } = this.state;
     const prettyValue = JSON.stringify(JSON.parse(stringValue), undefined, 2);
diff --git a/web-console/src/components/json-input/json-input.tsx b/web-console/src/components/json-input/json-input.tsx
index 0aff544..6ecea6b 100644
--- a/web-console/src/components/json-input/json-input.tsx
+++ b/web-console/src/components/json-input/json-input.tsx
@@ -58,7 +58,7 @@ export class JSONInput extends React.PureComponent<JSONInputProps, JSONInputStat
     }
   }
 
-  render() {
+  render(): JSX.Element {
     const { onChange, updateInputValidity, focus, width, height } = this.props;
     const { stringValue } = this.state;
     return (
diff --git a/web-console/src/components/loader/loader.tsx b/web-console/src/components/loader/loader.tsx
index 69d74be..ecf5b3c 100644
--- a/web-console/src/components/loader/loader.tsx
+++ b/web-console/src/components/loader/loader.tsx
@@ -28,7 +28,7 @@ export interface LoaderProps {
 export interface LoaderState {}
 
 export class Loader extends React.PureComponent<LoaderProps, LoaderState> {
-  render() {
+  render(): JSX.Element | null {
     const { loadingText, loading } = this.props;
     if (!loading) return null;
 
diff --git a/web-console/src/components/menu-checkbox/menu-checkbox.tsx b/web-console/src/components/menu-checkbox/menu-checkbox.tsx
index 270d34f..be1812a 100644
--- a/web-console/src/components/menu-checkbox/menu-checkbox.tsx
+++ b/web-console/src/components/menu-checkbox/menu-checkbox.tsx
@@ -22,7 +22,7 @@ import React from 'react';
 import './menu-checkbox.scss';
 
 export class MenuCheckbox extends React.PureComponent<ICheckboxProps> {
-  render() {
+  render(): JSX.Element {
     return (
       <li className="menu-checkbox">
         <Checkbox {...this.props} />
diff --git a/web-console/src/components/refresh-button/refresh-button.tsx b/web-console/src/components/refresh-button/refresh-button.tsx
index ab945e3..9393e50 100644
--- a/web-console/src/components/refresh-button/refresh-button.tsx
+++ b/web-console/src/components/refresh-button/refresh-button.tsx
@@ -31,7 +31,7 @@ export class RefreshButton extends React.PureComponent<RefreshButtonProps> {
     super(props, context);
   }
 
-  render() {
+  render(): JSX.Element {
     const { onRefresh, localStorageKey } = this.props;
     const intervals = [
       { label: '5 seconds', value: 5000 },
diff --git a/web-console/src/components/rule-editor/rule-editor.spec.tsx b/web-console/src/components/rule-editor/rule-editor.spec.tsx
index a3a1608..fc945a3 100644
--- a/web-console/src/components/rule-editor/rule-editor.spec.tsx
+++ b/web-console/src/components/rule-editor/rule-editor.spec.tsx
@@ -27,8 +27,8 @@ describe('rule editor', () => {
       <RuleEditor
         rule={{ type: 'loadForever' }}
         tiers={['test', 'test', 'test']}
-        onChange={() => null}
-        onDelete={() => null}
+        onChange={() => {}}
+        onDelete={() => {}}
         moveUp={null}
         moveDown={null}
       />
diff --git a/web-console/src/components/rule-editor/rule-editor.tsx b/web-console/src/components/rule-editor/rule-editor.tsx
index f46a42f..6ff916a 100644
--- a/web-console/src/components/rule-editor/rule-editor.tsx
+++ b/web-console/src/components/rule-editor/rule-editor.tsx
@@ -239,7 +239,7 @@ export class RuleEditor extends React.PureComponent<RuleEditorProps, RuleEditorS
     );
   }
 
-  render() {
+  render(): JSX.Element | null {
     const { onChange, rule, onDelete, moveUp, moveDown } = this.props;
     const { isOpen } = this.state;
 
diff --git a/web-console/src/components/show-json/show-json.tsx b/web-console/src/components/show-json/show-json.tsx
index 7b8e2cc..beace5e 100644
--- a/web-console/src/components/show-json/show-json.tsx
+++ b/web-console/src/components/show-json/show-json.tsx
@@ -64,7 +64,7 @@ export class ShowJson extends React.PureComponent<ShowJsonProps, ShowJsonState>
     }
   };
 
-  render() {
+  render(): JSX.Element {
     const { endpoint, downloadFilename } = this.props;
     const { jsonValue } = this.state;
 
diff --git a/web-console/src/components/show-log/show-log.tsx b/web-console/src/components/show-log/show-log.tsx
index 8efd2b0..801de24 100644
--- a/web-console/src/components/show-log/show-log.tsx
+++ b/web-console/src/components/show-log/show-log.tsx
@@ -39,7 +39,7 @@ export interface ShowLogProps {
   endpoint: string;
   downloadFilename?: string;
   tailOffset?: number;
-  status: string | null;
+  status?: string;
 }
 
 export interface ShowLogState {
@@ -102,7 +102,7 @@ export class ShowLog extends React.PureComponent<ShowLogProps, ShowLogState> {
     }
   };
 
-  render() {
+  render(): JSX.Element {
     const { endpoint, downloadFilename, status } = this.props;
     const { logValue } = this.state;
 
diff --git a/web-console/src/components/suggestible-input/suggestible-input.tsx b/web-console/src/components/suggestible-input/suggestible-input.tsx
index 47eb174..92e0e51 100644
--- a/web-console/src/components/suggestible-input/suggestible-input.tsx
+++ b/web-console/src/components/suggestible-input/suggestible-input.tsx
@@ -79,7 +79,7 @@ export class SuggestibleInput extends React.PureComponent<SuggestibleInputProps>
     );
   }
 
-  render() {
+  render(): JSX.Element {
     const { className, value, defaultValue, onValueChange, large, ...rest } = this.props;
     const suggestionsMenu = this.renderSuggestionsMenu();
 
diff --git a/web-console/src/components/table-cell/table-cell.tsx b/web-console/src/components/table-cell/table-cell.tsx
index e4164fd..5a6b535 100644
--- a/web-console/src/components/table-cell/table-cell.tsx
+++ b/web-console/src/components/table-cell/table-cell.tsx
@@ -69,7 +69,7 @@ export class TableCell extends React.PureComponent<NullTableCellProps> {
     };
   }
 
-  render() {
+  render(): React.ReactNode {
     const { value, timestamp, unparseable } = this.props;
     if (unparseable) {
       return <span className="table-cell unparseable">error</span>;
diff --git a/web-console/src/components/table-column-selector/table-column-selector.tsx b/web-console/src/components/table-column-selector/table-column-selector.tsx
index 2876404..efcf380 100644
--- a/web-console/src/components/table-column-selector/table-column-selector.tsx
+++ b/web-console/src/components/table-column-selector/table-column-selector.tsx
@@ -41,7 +41,7 @@ export class TableColumnSelector extends React.PureComponent<
     this.state = {};
   }
 
-  render() {
+  render(): JSX.Element {
     const { columns, onChange, tableColumnsHidden } = this.props;
     const checkboxes = (
       <Menu className="table-column-selector-menu">
diff --git a/web-console/src/components/timed-button/timed-button.tsx b/web-console/src/components/timed-button/timed-button.tsx
index 7b5d957..3da95d2 100644
--- a/web-console/src/components/timed-button/timed-button.tsx
+++ b/web-console/src/components/timed-button/timed-button.tsx
@@ -92,7 +92,7 @@ export class TimedButton extends React.PureComponent<TimedButtonProps, TimedButt
     this.continuousRefresh(selectedInterval);
   };
 
-  render() {
+  render(): JSX.Element {
     const {
       label,
       intervals,
diff --git a/web-console/src/components/view-control-bar/view-control-bar.tsx b/web-console/src/components/view-control-bar/view-control-bar.tsx
index 461e3b8..4dae002 100644
--- a/web-console/src/components/view-control-bar/view-control-bar.tsx
+++ b/web-console/src/components/view-control-bar/view-control-bar.tsx
@@ -29,7 +29,7 @@ export class ViewControlBar extends React.PureComponent<ViewControlBarProps> {
     super(props);
   }
 
-  render() {
+  render(): JSX.Element {
     const { label, children } = this.props;
 
     return (
diff --git a/web-console/src/console-application.tsx b/web-console/src/console-application.tsx
index 636c706..08f253a 100644
--- a/web-console/src/console-application.tsx
+++ b/web-console/src/console-application.tsx
@@ -284,7 +284,7 @@ export class ConsoleApplication extends React.PureComponent<
     return this.wrapInViewContainer('lookups', <LookupsView />);
   };
 
-  render() {
+  render(): JSX.Element {
     const { capabilitiesLoading } = this.state;
 
     if (capabilitiesLoading) {
diff --git a/web-console/src/dialogs/about-dialog/about-dialog.spec.tsx b/web-console/src/dialogs/about-dialog/about-dialog.spec.tsx
index f11fd2b..69a5068 100644
--- a/web-console/src/dialogs/about-dialog/about-dialog.spec.tsx
+++ b/web-console/src/dialogs/about-dialog/about-dialog.spec.tsx
@@ -23,7 +23,7 @@ import { AboutDialog } from './about-dialog';
 
 describe('about dialog', () => {
   it('matches snapshot', () => {
-    const aboutDialog = <AboutDialog onClose={() => null} />;
+    const aboutDialog = <AboutDialog onClose={() => {}} />;
     render(aboutDialog);
     expect(document.body.lastChild).toMatchSnapshot();
   });
diff --git a/web-console/src/dialogs/about-dialog/about-dialog.tsx b/web-console/src/dialogs/about-dialog/about-dialog.tsx
index fe2ea9d..7bbb549 100644
--- a/web-console/src/dialogs/about-dialog/about-dialog.tsx
+++ b/web-console/src/dialogs/about-dialog/about-dialog.tsx
@@ -40,7 +40,7 @@ export class AboutDialog extends React.PureComponent<AboutDialogProps, AboutDial
     this.state = {};
   }
 
-  render() {
+  render(): JSX.Element {
     const { onClose } = this.props;
 
     return (
diff --git a/web-console/src/dialogs/async-action-dialog/async-action-dialog.spec.tsx b/web-console/src/dialogs/async-action-dialog/async-action-dialog.spec.tsx
index b54d204..d09c793 100644
--- a/web-console/src/dialogs/async-action-dialog/async-action-dialog.spec.tsx
+++ b/web-console/src/dialogs/async-action-dialog/async-action-dialog.spec.tsx
@@ -28,7 +28,7 @@ describe('async action dialog', () => {
         action={() => {
           return Promise.resolve();
         }}
-        onClose={() => null}
+        onClose={() => {}}
         confirmButtonText={'test'}
         successText={'test'}
         failText={'test'}
diff --git a/web-console/src/dialogs/async-action-dialog/async-action-dialog.tsx b/web-console/src/dialogs/async-action-dialog/async-action-dialog.tsx
index 9891d1c..f426df1 100644
--- a/web-console/src/dialogs/async-action-dialog/async-action-dialog.tsx
+++ b/web-console/src/dialogs/async-action-dialog/async-action-dialog.tsx
@@ -106,7 +106,7 @@ export class AsyncActionDialog extends React.PureComponent<
     onClose();
   };
 
-  render() {
+  render(): JSX.Element {
     const {
       className,
       icon,
diff --git a/web-console/src/dialogs/compaction-dialog/compaction-dialog.spec.tsx b/web-console/src/dialogs/compaction-dialog/compaction-dialog.spec.tsx
index ad3ef3c..44ae80b 100644
--- a/web-console/src/dialogs/compaction-dialog/compaction-dialog.spec.tsx
+++ b/web-console/src/dialogs/compaction-dialog/compaction-dialog.spec.tsx
@@ -25,9 +25,9 @@ describe('compaction dialog', () => {
   it('matches snapshot', () => {
     const compactionDialog = (
       <CompactionDialog
-        onClose={() => null}
-        onSave={() => null}
-        onDelete={() => null}
+        onClose={() => {}}
+        onSave={() => {}}
+        onDelete={() => {}}
         datasource={'test'}
         configData={'test'}
       />
diff --git a/web-console/src/dialogs/compaction-dialog/compaction-dialog.tsx b/web-console/src/dialogs/compaction-dialog/compaction-dialog.tsx
index f5d9508..46976a5 100644
--- a/web-console/src/dialogs/compaction-dialog/compaction-dialog.tsx
+++ b/web-console/src/dialogs/compaction-dialog/compaction-dialog.tsx
@@ -32,7 +32,7 @@ export interface CompactionDialogProps {
 }
 
 export interface CompactionDialogState {
-  currentConfig: Record<string, any> | null;
+  currentConfig?: Record<string, any>;
   allJSONValid: boolean;
 }
 
@@ -43,7 +43,6 @@ export class CompactionDialog extends React.PureComponent<
   constructor(props: CompactionDialogProps) {
     super(props);
     this.state = {
-      currentConfig: null,
       allJSONValid: true,
     };
   }
@@ -68,7 +67,7 @@ export class CompactionDialog extends React.PureComponent<
     });
   }
 
-  render() {
+  render(): JSX.Element {
     const { onClose, onSave, onDelete, datasource, configData } = this.props;
     const { currentConfig, allJSONValid } = this.state;
     return (
@@ -127,7 +126,7 @@ export class CompactionDialog extends React.PureComponent<
               text="Submit"
               intent={Intent.PRIMARY}
               onClick={() => onSave(currentConfig)}
-              disabled={currentConfig === null || !allJSONValid}
+              disabled={!currentConfig || !allJSONValid}
             />
           </div>
         </div>
diff --git a/web-console/src/dialogs/coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog.spec.tsx b/web-console/src/dialogs/coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog.spec.tsx
index ad48adb..3b865ea 100644
--- a/web-console/src/dialogs/coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog.spec.tsx
+++ b/web-console/src/dialogs/coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog.spec.tsx
@@ -23,7 +23,7 @@ import { CoordinatorDynamicConfigDialog } from './coordinator-dynamic-config-dia
 
 describe('coordinator dynamic config', () => {
   it('matches snapshot', () => {
-    const coordinatorDynamicConfig = <CoordinatorDynamicConfigDialog onClose={() => null} />;
+    const coordinatorDynamicConfig = <CoordinatorDynamicConfigDialog onClose={() => {}} />;
     render(coordinatorDynamicConfig);
     expect(document.body.lastChild).toMatchSnapshot();
   });
diff --git a/web-console/src/dialogs/coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog.tsx b/web-console/src/dialogs/coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog.tsx
index 7d70487..18818ca 100644
--- a/web-console/src/dialogs/coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog.tsx
+++ b/web-console/src/dialogs/coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog.tsx
@@ -33,7 +33,7 @@ export interface CoordinatorDynamicConfigDialogProps {
 }
 
 export interface CoordinatorDynamicConfigDialogState {
-  dynamicConfig: Record<string, any> | null;
+  dynamicConfig?: Record<string, any>;
   historyRecords: any[];
 }
 
@@ -46,7 +46,6 @@ export class CoordinatorDynamicConfigDialog extends React.PureComponent<
   constructor(props: CoordinatorDynamicConfigDialogProps) {
     super(props);
     this.state = {
-      dynamicConfig: null,
       historyRecords: [],
     };
 
@@ -70,7 +69,7 @@ export class CoordinatorDynamicConfigDialog extends React.PureComponent<
   }
 
   async getClusterConfig() {
-    let config: Record<string, any> | null = null;
+    let config: Record<string, any> | undefined;
     try {
       const configResp = await axios.get('/druid/coordinator/v1/config');
       config = configResp.data;
@@ -112,7 +111,7 @@ export class CoordinatorDynamicConfigDialog extends React.PureComponent<
     onClose();
   };
 
-  render() {
+  render(): JSX.Element {
     const { onClose } = this.props;
     const { dynamicConfig, historyRecords } = this.state;
 
diff --git a/web-console/src/dialogs/lookup-edit-dialog/lookup-edit-dialog.spec.tsx b/web-console/src/dialogs/lookup-edit-dialog/lookup-edit-dialog.spec.tsx
index 0171c9b..0793637 100644
--- a/web-console/src/dialogs/lookup-edit-dialog/lookup-edit-dialog.spec.tsx
+++ b/web-console/src/dialogs/lookup-edit-dialog/lookup-edit-dialog.spec.tsx
@@ -26,9 +26,9 @@ describe('lookup edit dialog', () => {
     const lookupEditDialog = (
       <LookupEditDialog
         isOpen
-        onClose={() => null}
-        onSubmit={() => null}
-        onChange={() => null}
+        onClose={() => {}}
+        onSubmit={() => {}}
+        onChange={() => {}}
         lookupName={'test'}
         lookupTier={'test'}
         lookupVersion={'test'}
diff --git a/web-console/src/dialogs/lookup-edit-dialog/lookup-edit-dialog.tsx b/web-console/src/dialogs/lookup-edit-dialog/lookup-edit-dialog.tsx
index 180ec2b..4abd892 100644
--- a/web-console/src/dialogs/lookup-edit-dialog/lookup-edit-dialog.tsx
+++ b/web-console/src/dialogs/lookup-edit-dialog/lookup-edit-dialog.tsx
@@ -94,7 +94,7 @@ export class LookupEditDialog extends React.PureComponent<
     }
   }
 
-  render() {
+  render(): JSX.Element {
     const {
       isOpen,
       onClose,
@@ -143,7 +143,7 @@ export class LookupEditDialog extends React.PureComponent<
 
         <AceEditor
           className="lookup-edit-dialog-textarea"
-          mode="sql"
+          mode="hjson"
           theme="solarized_dark"
           onChange={(e: any) => onChange('lookupEditSpec', e)}
           fontSize={12}
diff --git a/web-console/src/dialogs/overlord-dynamic-config-dialog/overload-dynamic-config-dialog.spec.tsx b/web-console/src/dialogs/overlord-dynamic-config-dialog/overload-dynamic-config-dialog.spec.tsx
index 7b60cdb..5b84552 100644
--- a/web-console/src/dialogs/overlord-dynamic-config-dialog/overload-dynamic-config-dialog.spec.tsx
+++ b/web-console/src/dialogs/overlord-dynamic-config-dialog/overload-dynamic-config-dialog.spec.tsx
@@ -23,7 +23,7 @@ import { OverlordDynamicConfigDialog } from './overlord-dynamic-config-dialog';
 
 describe('overload dynamic config', () => {
   it('matches snapshot', () => {
-    const lookupEditDialog = <OverlordDynamicConfigDialog onClose={() => null} />;
+    const lookupEditDialog = <OverlordDynamicConfigDialog onClose={() => {}} />;
 
     render(lookupEditDialog);
     expect(document.body.lastChild).toMatchSnapshot();
diff --git a/web-console/src/dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog.tsx b/web-console/src/dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog.tsx
index dcf9423..ad0ea17 100644
--- a/web-console/src/dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog.tsx
+++ b/web-console/src/dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog.tsx
@@ -33,7 +33,7 @@ export interface OverlordDynamicConfigDialogProps {
 }
 
 export interface OverlordDynamicConfigDialogState {
-  dynamicConfig: Record<string, any> | null;
+  dynamicConfig?: Record<string, any>;
   allJSONValid: boolean;
   historyRecords: any[];
 }
@@ -47,7 +47,6 @@ export class OverlordDynamicConfigDialog extends React.PureComponent<
   constructor(props: OverlordDynamicConfigDialogProps) {
     super(props);
     this.state = {
-      dynamicConfig: null,
       allJSONValid: true,
       historyRecords: [],
     };
@@ -72,7 +71,7 @@ export class OverlordDynamicConfigDialog extends React.PureComponent<
   }
 
   async getConfig() {
-    let config: Record<string, any> | null = null;
+    let config: Record<string, any> | undefined;
     try {
       const configResp = await axios.get('/druid/indexer/v1/worker');
       config = configResp.data || {};
@@ -114,7 +113,7 @@ export class OverlordDynamicConfigDialog extends React.PureComponent<
     onClose();
   };
 
-  render() {
+  render(): JSX.Element {
     const { onClose } = this.props;
     const { dynamicConfig, allJSONValid, historyRecords } = this.state;
 
diff --git a/web-console/src/dialogs/query-plan-dialog/query-plan-dialog.spec.tsx b/web-console/src/dialogs/query-plan-dialog/query-plan-dialog.spec.tsx
index 59cc19f..465436d 100644
--- a/web-console/src/dialogs/query-plan-dialog/query-plan-dialog.spec.tsx
+++ b/web-console/src/dialogs/query-plan-dialog/query-plan-dialog.spec.tsx
@@ -24,11 +24,7 @@ import { QueryPlanDialog } from './query-plan-dialog';
 describe('query plan dialog', () => {
   it('matches snapshot', () => {
     const queryPlanDialog = (
-      <QueryPlanDialog
-        explainResult={'test'}
-        explainError={{ name: 'test', message: 'test' }}
-        onClose={() => null}
-      />
+      <QueryPlanDialog explainResult={'test'} explainError={undefined} onClose={() => {}} />
     );
     render(queryPlanDialog);
     expect(document.body.lastChild).toMatchSnapshot();
diff --git a/web-console/src/dialogs/query-plan-dialog/query-plan-dialog.tsx b/web-console/src/dialogs/query-plan-dialog/query-plan-dialog.tsx
index 8fa438b..94bcdf3 100644
--- a/web-console/src/dialogs/query-plan-dialog/query-plan-dialog.tsx
+++ b/web-console/src/dialogs/query-plan-dialog/query-plan-dialog.tsx
@@ -24,8 +24,8 @@ import { BasicQueryExplanation, SemiJoinQueryExplanation } from '../../utils';
 import './query-plan-dialog.scss';
 
 export interface QueryPlanDialogProps {
-  explainResult: BasicQueryExplanation | SemiJoinQueryExplanation | string | null;
-  explainError: Error | null;
+  explainResult?: BasicQueryExplanation | SemiJoinQueryExplanation | string;
+  explainError?: string;
   onClose: () => void;
 }
 
@@ -40,17 +40,17 @@ export class QueryPlanDialog extends React.PureComponent<
     this.state = {};
   }
 
-  render() {
+  render(): JSX.Element {
     const { explainResult, explainError, onClose } = this.props;
 
     let content: JSX.Element;
 
     if (explainError) {
-      content = <div>{explainError.message}</div>;
-    } else if (explainResult == null) {
+      content = <div>{explainError}</div>;
+    } else if (!explainResult) {
       content = <div />;
     } else if ((explainResult as BasicQueryExplanation).query) {
-      let signature: JSX.Element | null = null;
+      let signature: JSX.Element | undefined;
       if ((explainResult as BasicQueryExplanation).signature) {
         const signatureContent = (explainResult as BasicQueryExplanation).signature || '';
         signature = (
@@ -79,8 +79,8 @@ export class QueryPlanDialog extends React.PureComponent<
       (explainResult as SemiJoinQueryExplanation).mainQuery &&
       (explainResult as SemiJoinQueryExplanation).subQueryRight
     ) {
-      let mainSignature: JSX.Element | null = null;
-      let subSignature: JSX.Element | null = null;
+      let mainSignature: JSX.Element | undefined;
+      let subSignature: JSX.Element | undefined;
       if ((explainResult as SemiJoinQueryExplanation).mainQuery.signature) {
         const signatureContent =
           (explainResult as SemiJoinQueryExplanation).mainQuery.signature || '';
diff --git a/web-console/src/dialogs/retention-dialog/retention-dialog.spec.tsx b/web-console/src/dialogs/retention-dialog/retention-dialog.spec.tsx
index cc8d0ae..2372b39 100644
--- a/web-console/src/dialogs/retention-dialog/retention-dialog.spec.tsx
+++ b/web-console/src/dialogs/retention-dialog/retention-dialog.spec.tsx
@@ -28,9 +28,9 @@ describe('retention dialog', () => {
         datasource={'test'}
         rules={[null]}
         tiers={['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']}
-        onEditDefaults={() => null}
-        onCancel={() => null}
-        onSave={() => null}
+        onEditDefaults={() => {}}
+        onCancel={() => {}}
+        onSave={() => {}}
       />
     );
     render(retentionDialog);
diff --git a/web-console/src/dialogs/retention-dialog/retention-dialog.tsx b/web-console/src/dialogs/retention-dialog/retention-dialog.tsx
index f75a3d1..790ff06 100644
--- a/web-console/src/dialogs/retention-dialog/retention-dialog.tsx
+++ b/web-console/src/dialogs/retention-dialog/retention-dialog.tsx
@@ -163,7 +163,7 @@ export class RetentionDialog extends React.PureComponent<
     });
   };
 
-  render() {
+  render(): JSX.Element {
     const { datasource, onCancel, onEditDefaults } = this.props;
     const { currentRules, historyRecords } = this.state;
 
diff --git a/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.spec.tsx b/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.spec.tsx
index a722986..e51a732 100644
--- a/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.spec.tsx
+++ b/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.spec.tsx
@@ -28,7 +28,7 @@ describe('task table action dialog', () => {
         dataSourceId="test"
         segmentId="test"
         actions={[{ title: 'test', onAction: () => null }]}
-        onClose={() => null}
+        onClose={() => {}}
         isOpen
       />
     );
diff --git a/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.tsx b/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.tsx
index 3dbfef8..37f4702 100644
--- a/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.tsx
+++ b/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.tsx
@@ -24,8 +24,8 @@ import { BasicAction, basicActionsToButtons } from '../../utils/basic-action';
 import { SideButtonMetaData, TableActionDialog } from '../table-action-dialog/table-action-dialog';
 
 interface SegmentTableActionDialogProps extends IDialogProps {
-  segmentId: string | null;
-  dataSourceId: string | null;
+  segmentId?: string;
+  dataSourceId?: string;
   actions: BasicAction[];
   onClose: () => void;
 }
diff --git a/web-console/src/dialogs/show-value-dialog/show-value-dialog.spec.tsx b/web-console/src/dialogs/show-value-dialog/show-value-dialog.spec.tsx
index 8a43718..553cabb 100644
--- a/web-console/src/dialogs/show-value-dialog/show-value-dialog.spec.tsx
+++ b/web-console/src/dialogs/show-value-dialog/show-value-dialog.spec.tsx
@@ -25,7 +25,7 @@ describe('clipboard dialog', () => {
   it('matches snapshot', () => {
     const compactionDialog = (
       <ShowValueDialog
-        onClose={() => null}
+        onClose={() => {}}
         str={
           'Bot: Automatska zamjena teksta  (-[[Administrativna podjela Meksika|Admin]] +[[Administrativna podjela Meksika|Admi]])'
         }
diff --git a/web-console/src/dialogs/show-value-dialog/show-value-dialog.tsx b/web-console/src/dialogs/show-value-dialog/show-value-dialog.tsx
index 9aed8b0..60f4a2f 100644
--- a/web-console/src/dialogs/show-value-dialog/show-value-dialog.tsx
+++ b/web-console/src/dialogs/show-value-dialog/show-value-dialog.tsx
@@ -35,7 +35,7 @@ export class ShowValueDialog extends React.PureComponent<ShowValueDialogProps> {
     this.state = {};
   }
 
-  render() {
+  render(): JSX.Element {
     const { onClose, str } = this.props;
 
     return (
diff --git a/web-console/src/dialogs/snitch-dialog/snitch-dialog.spec.tsx b/web-console/src/dialogs/snitch-dialog/snitch-dialog.spec.tsx
index fd5cbd6..1c73826 100644
--- a/web-console/src/dialogs/snitch-dialog/snitch-dialog.spec.tsx
+++ b/web-console/src/dialogs/snitch-dialog/snitch-dialog.spec.tsx
@@ -23,7 +23,7 @@ import { SnitchDialog } from './snitch-dialog';
 
 describe('snitch dialog', () => {
   it('matches snapshot', () => {
-    const snitchDialog = <SnitchDialog onSave={() => null} isOpen />;
+    const snitchDialog = <SnitchDialog onSave={() => {}} isOpen />;
     render(snitchDialog);
     expect(document.body.lastChild).toMatchSnapshot();
   });
diff --git a/web-console/src/dialogs/snitch-dialog/snitch-dialog.tsx b/web-console/src/dialogs/snitch-dialog/snitch-dialog.tsx
index 742ad55..fc8b4f4 100644
--- a/web-console/src/dialogs/snitch-dialog/snitch-dialog.tsx
+++ b/web-console/src/dialogs/snitch-dialog/snitch-dialog.tsx
@@ -116,9 +116,10 @@ export class SnitchDialog extends React.PureComponent<SnitchDialogProps, SnitchD
     );
   }
 
-  renderHistoryDialog() {
+  renderHistoryDialog(): JSX.Element | null {
     const { historyRecords } = this.props;
-    if (!historyRecords) return;
+    if (!historyRecords) return null;
+
     return (
       <HistoryDialog {...this.props} className="history-dialog" historyRecords={historyRecords}>
         <div className={Classes.DIALOG_FOOTER_ACTIONS}>
@@ -150,7 +151,7 @@ export class SnitchDialog extends React.PureComponent<SnitchDialogProps, SnitchD
             Back
           </Button>
         ) : onReset ? (
-          <Button onClick={this.reset} intent={'none' as any}>
+          <Button onClick={this.reset} intent={Intent.NONE}>
             Reset
           </Button>
         ) : null}
@@ -176,7 +177,7 @@ export class SnitchDialog extends React.PureComponent<SnitchDialogProps, SnitchD
     );
   }
 
-  render() {
+  render(): JSX.Element | null {
     const { children, saveDisabled } = this.props;
     const { showFinalStep, showHistory } = this.state;
 
diff --git a/web-console/src/dialogs/spec-dialog/spec-dialog.spec.tsx b/web-console/src/dialogs/spec-dialog/spec-dialog.spec.tsx
index 7cb93fa..65d576d 100644
--- a/web-console/src/dialogs/spec-dialog/spec-dialog.spec.tsx
+++ b/web-console/src/dialogs/spec-dialog/spec-dialog.spec.tsx
@@ -23,7 +23,7 @@ import { SpecDialog } from './spec-dialog';
 
 describe('spec dialog', () => {
   it('matches snapshot', () => {
-    const specDialog = <SpecDialog onSubmit={() => null} onClose={() => null} title={'test'} />;
+    const specDialog = <SpecDialog onSubmit={() => {}} onClose={() => {}} title={'test'} />;
     render(specDialog);
     expect(document.body.lastChild).toMatchSnapshot();
   });
diff --git a/web-console/src/dialogs/spec-dialog/spec-dialog.tsx b/web-console/src/dialogs/spec-dialog/spec-dialog.tsx
index 5cee087..b1a14f8 100644
--- a/web-console/src/dialogs/spec-dialog/spec-dialog.tsx
+++ b/web-console/src/dialogs/spec-dialog/spec-dialog.tsx
@@ -58,7 +58,7 @@ export class SpecDialog extends React.PureComponent<SpecDialogProps, SpecDialogS
     onClose();
   }
 
-  render() {
+  render(): JSX.Element {
     const { onClose, title } = this.props;
     const { spec } = this.state;
 
diff --git a/web-console/src/dialogs/supervisor-table-action-dialog/__snapshots__/supervisor-table-action-dialog.spec.tsx.snap b/web-console/src/dialogs/supervisor-table-action-dialog/__snapshots__/supervisor-table-action-dialog.spec.tsx.snap
index 84017de..1fd8ebf 100755
--- a/web-console/src/dialogs/supervisor-table-action-dialog/__snapshots__/supervisor-table-action-dialog.spec.tsx.snap
+++ b/web-console/src/dialogs/supervisor-table-action-dialog/__snapshots__/supervisor-table-action-dialog.spec.tsx.snap
@@ -92,20 +92,20 @@ exports[`supervisor table action dialog matches snapshot 1`] = `
               type="button"
             >
               <span
-                class="bp3-icon bp3-icon-align-left"
-                icon="align-left"
+                class="bp3-icon bp3-icon-chart"
+                icon="chart"
               >
                 <svg
-                  data-icon="align-left"
+                  data-icon="chart"
                   height="20"
                   viewBox="0 0 20 20"
                   width="20"
                 >
                   <desc>
-                    align-left
+                    chart
                   </desc>
                   <path
-                    d="M1 7h10c.55 0 1-.45 1-1s-.45-1-1-1H1c-.55 0-1 .45-1 1s.45 1 1 1zm0-4h18c.55 0 1-.45 1-1s-.45-1-1-1H1c-.55 0-1 .45-1 1s.45 1 1 1zm14 14H1c-.55 0-1 .45-1 1s.45 1 1 1h14c.55 0 1-.45 1-1s-.45-1-1-1zm4-8H1c-.55 0-1 .45-1 1s.45 1 1 1h18c.55 0 1-.45 1-1s-.45-1-1-1zM1 15h6c.55 0 1-.45 1-1s-.45-1-1-1H1c-.55 0-1 .45-1 1s.45 1 1 1z"
+                    d="M7 11v8c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-8l-2 2-4-2zm-7 8c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-8l-6 3v5zM17 7l-3 3v9c0 .55.45 1 1 1h4c.55 0 1-.45 1-1V8.74c-.26.15-.58.26-1 .26-1.92 0-2-2-2-2zm2-6h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59L10.8 8.78 7.45 7.11v.01C7.31 7.05 7.16 7 7 7s-.31.05-.44.11V7.1l-6 3v.01c-.33.17-.56.5-.56.89 0 .55.45 1 1 1 .16 0 .31-.05.44-.11v.01L7 9.12l3.55 1.78v-.01c.14.06.29.11.45.11.28 0 .53-.11.71-.29L18 4.41V6c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.4 [...]
                     fill-rule="evenodd"
                   />
                 </svg>
@@ -113,7 +113,7 @@ exports[`supervisor table action dialog matches snapshot 1`] = `
               <span
                 class="bp3-button-text"
               >
-                Payload
+                Statistics
               </span>
             </button>
             <button
@@ -121,20 +121,20 @@ exports[`supervisor table action dialog matches snapshot 1`] = `
               type="button"
             >
               <span
-                class="bp3-icon bp3-icon-chart"
-                icon="chart"
+                class="bp3-icon bp3-icon-align-left"
+                icon="align-left"
               >
                 <svg
-                  data-icon="chart"
+                  data-icon="align-left"
                   height="20"
                   viewBox="0 0 20 20"
                   width="20"
                 >
                   <desc>
-                    chart
+                    align-left
                   </desc>
                   <path
-                    d="M7 11v8c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-8l-2 2-4-2zm-7 8c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-8l-6 3v5zM17 7l-3 3v9c0 .55.45 1 1 1h4c.55 0 1-.45 1-1V8.74c-.26.15-.58.26-1 .26-1.92 0-2-2-2-2zm2-6h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59L10.8 8.78 7.45 7.11v.01C7.31 7.05 7.16 7 7 7s-.31.05-.44.11V7.1l-6 3v.01c-.33.17-.56.5-.56.89 0 .55.45 1 1 1 .16 0 .31-.05.44-.11v.01L7 9.12l3.55 1.78v-.01c.14.06.29.11.45.11.28 0 .53-.11.71-.29L18 4.41V6c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.4 [...]
+                    d="M1 7h10c.55 0 1-.45 1-1s-.45-1-1-1H1c-.55 0-1 .45-1 1s.45 1 1 1zm0-4h18c.55 0 1-.45 1-1s-.45-1-1-1H1c-.55 0-1 .45-1 1s.45 1 1 1zm14 14H1c-.55 0-1 .45-1 1s.45 1 1 1h14c.55 0 1-.45 1-1s-.45-1-1-1zm4-8H1c-.55 0-1 .45-1 1s.45 1 1 1h18c.55 0 1-.45 1-1s-.45-1-1-1zM1 15h6c.55 0 1-.45 1-1s-.45-1-1-1H1c-.55 0-1 .45-1 1s.45 1 1 1z"
                     fill-rule="evenodd"
                   />
                 </svg>
@@ -142,7 +142,7 @@ exports[`supervisor table action dialog matches snapshot 1`] = `
               <span
                 class="bp3-button-text"
               >
-                Statistics
+                Payload
               </span>
             </button>
             <button
diff --git a/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.spec.tsx b/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.spec.tsx
index 5fdb45b..18ef377 100644
--- a/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.spec.tsx
+++ b/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.spec.tsx
@@ -29,7 +29,7 @@ describe('supervisor table action dialog', () => {
       <SupervisorTableActionDialog
         supervisorId={'test'}
         actions={[basicAction, basicAction, basicAction, basicAction]}
-        onClose={() => null}
+        onClose={() => {}}
         isOpen
       />
     );
diff --git a/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.tsx b/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.tsx
index 49d6af4..84a10b0 100644
--- a/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.tsx
+++ b/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.tsx
@@ -31,7 +31,7 @@ interface SupervisorTableActionDialogProps extends IDialogProps {
 }
 
 interface SupervisorTableActionDialogState {
-  activeTab: 'status' | 'payload' | 'stats' | 'history';
+  activeTab: 'status' | 'stats' | 'payload' | 'history';
 }
 
 export class SupervisorTableActionDialog extends React.PureComponent<
@@ -56,18 +56,18 @@ export class SupervisorTableActionDialog extends React.PureComponent<
         onClick: () => this.setState({ activeTab: 'status' }),
       },
       {
-        icon: 'align-left',
-        text: 'Payload',
-        active: activeTab === 'payload',
-        onClick: () => this.setState({ activeTab: 'payload' }),
-      },
-      {
         icon: 'chart',
         text: 'Statistics',
         active: activeTab === 'stats',
         onClick: () => this.setState({ activeTab: 'stats' }),
       },
       {
+        icon: 'align-left',
+        text: 'Payload',
+        active: activeTab === 'payload',
+        onClick: () => this.setState({ activeTab: 'payload' }),
+      },
+      {
         icon: 'history',
         text: 'History',
         active: activeTab === 'history',
@@ -90,18 +90,18 @@ export class SupervisorTableActionDialog extends React.PureComponent<
             downloadFilename={`supervisor-status-${supervisorId}.json`}
           />
         )}
-        {activeTab === 'payload' && (
-          <ShowJson
-            endpoint={`/druid/indexer/v1/supervisor/${supervisorId}`}
-            downloadFilename={`supervisor-payload-${supervisorId}.json`}
-          />
-        )}
         {activeTab === 'stats' && (
           <ShowJson
             endpoint={`/druid/indexer/v1/supervisor/${supervisorId}/stats`}
             downloadFilename={`supervisor-stats-${supervisorId}.json`}
           />
         )}
+        {activeTab === 'payload' && (
+          <ShowJson
+            endpoint={`/druid/indexer/v1/supervisor/${supervisorId}`}
+            downloadFilename={`supervisor-payload-${supervisorId}.json`}
+          />
+        )}
         {activeTab === 'history' && (
           <ShowJson
             endpoint={`/druid/indexer/v1/supervisor/${supervisorId}/history`}
diff --git a/web-console/src/dialogs/table-action-dialog/table-action-dialog.spec.tsx b/web-console/src/dialogs/table-action-dialog/table-action-dialog.spec.tsx
index 48eb547..5e5ff8b 100644
--- a/web-console/src/dialogs/table-action-dialog/table-action-dialog.spec.tsx
+++ b/web-console/src/dialogs/table-action-dialog/table-action-dialog.spec.tsx
@@ -26,7 +26,7 @@ describe('table action dialog', () => {
     const tableActionDialog = (
       <TableActionDialog
         sideButtonMetadata={[{ icon: 'badge', text: 'test' }]}
-        onClose={() => null}
+        onClose={() => {}}
         isOpen
       />
     );
diff --git a/web-console/src/dialogs/table-action-dialog/table-action-dialog.tsx b/web-console/src/dialogs/table-action-dialog/table-action-dialog.tsx
index b8cd596..1e9209e 100644
--- a/web-console/src/dialogs/table-action-dialog/table-action-dialog.tsx
+++ b/web-console/src/dialogs/table-action-dialog/table-action-dialog.tsx
@@ -40,7 +40,7 @@ export class TableActionDialog extends React.PureComponent<TableActionDialogProp
     this.state = {};
   }
 
-  render() {
+  render(): JSX.Element {
     const { sideButtonMetadata, isOpen, onClose, title, bottomButtons } = this.props;
 
     return (
diff --git a/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.spec.tsx b/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.spec.tsx
index 54e730f..adaacd6 100644
--- a/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.spec.tsx
+++ b/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.spec.tsx
@@ -29,7 +29,7 @@ describe('task table action dialog', () => {
         status={'RUNNING'}
         taskId={'test'}
         actions={[basicAction]}
-        onClose={() => null}
+        onClose={() => {}}
         isOpen
       />
     );
diff --git a/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.tsx b/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.tsx
index c6c5540..be2faae 100644
--- a/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.tsx
+++ b/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.tsx
@@ -28,7 +28,7 @@ interface TaskTableActionDialogProps extends IDialogProps {
   taskId: string;
   actions: BasicAction[];
   onClose: () => void;
-  status: string | null;
+  status?: string;
 }
 
 interface TaskTableActionDialogState {
diff --git a/web-console/src/utils/druid-query.ts b/web-console/src/utils/druid-query.ts
index 831d2a9..db22700 100644
--- a/web-console/src/utils/druid-query.ts
+++ b/web-console/src/utils/druid-query.ts
@@ -18,11 +18,12 @@
 
 import axios from 'axios';
 import { AxiosResponse } from 'axios';
+import compact from 'lodash.compact';
 
-export function parseHtmlError(htmlStr: string): string | null {
+export function parseHtmlError(htmlStr: string): string | undefined {
   const startIndex = htmlStr.indexOf('</h3><pre>');
   const endIndex = htmlStr.indexOf('\n\tat');
-  if (startIndex === -1 || endIndex === -1) return null;
+  if (startIndex === -1 || endIndex === -1) return;
 
   return htmlStr
     .substring(startIndex + 10, endIndex)
@@ -36,9 +37,12 @@ export function getDruidErrorMessage(e: any) {
   switch (typeof data) {
     case 'object':
       return (
-        [data.error, data.errorMessage, data.errorClass, data.host ? `on host ${data.host}` : null]
-          .filter(Boolean)
-          .join(' / ') || e.message
+        compact([
+          data.error,
+          data.errorMessage,
+          data.errorClass,
+          data.host ? `on host ${data.host}` : undefined,
+        ]).join(' / ') || e.message
       );
 
     case 'string':
diff --git a/web-console/src/utils/druid-type.ts b/web-console/src/utils/druid-type.ts
index 955c8a2..b0540d3 100644
--- a/web-console/src/utils/druid-type.ts
+++ b/web-console/src/utils/druid-type.ts
@@ -42,7 +42,7 @@ export function getColumnTypeFromHeaderAndRows(
   column: string,
 ): string {
   return guessTypeFromSample(
-    filterMap(headerAndRows.rows, (r: any) => (r.parsed ? r.parsed[column] : null)),
+    filterMap(headerAndRows.rows, (r: any) => (r.parsed ? r.parsed[column] : undefined)),
   );
 }
 
diff --git a/web-console/src/utils/general.tsx b/web-console/src/utils/general.tsx
index ef75edc..b504420 100644
--- a/web-console/src/utils/general.tsx
+++ b/web-console/src/utils/general.tsx
@@ -101,10 +101,7 @@ function getNeedleAndMode(input: string): NeedleAndMode {
 }
 
 export function booleanCustomTableFilter(filter: Filter, value: any): boolean {
-  if (value === undefined) {
-    return true;
-  }
-  if (value === null) return false;
+  if (value == null) return false;
   const haystack = String(value).toLowerCase();
   const needleAndMode: NeedleAndMode = getNeedleAndMode(filter.value.toLowerCase());
   const needle = needleAndMode.needle;
diff --git a/web-console/src/utils/ingestion-spec.tsx b/web-console/src/utils/ingestion-spec.tsx
index f39ceaa..6caf518 100644
--- a/web-console/src/utils/ingestion-spec.tsx
+++ b/web-console/src/utils/ingestion-spec.tsx
@@ -133,7 +133,7 @@ export function getIngestionImage(ingestionType: IngestionComboTypeWithExtra): s
   return ingestionType;
 }
 
-export function getRequiredModule(ingestionType: IngestionComboTypeWithExtra): string | null {
+export function getRequiredModule(ingestionType: IngestionComboTypeWithExtra): string | undefined {
   switch (ingestionType) {
     case 'index:static-s3':
       return 'druid-s3-extensions';
@@ -148,7 +148,7 @@ export function getRequiredModule(ingestionType: IngestionComboTypeWithExtra): s
       return 'druid-kinesis-indexing-service';
 
     default:
-      return null;
+      return;
   }
 }
 
@@ -308,9 +308,9 @@ export function getParseSpecFormFields() {
   return PARSE_SPEC_FORM_FIELDS;
 }
 
-export function issueWithParser(parser: Parser | undefined): string | null {
+export function issueWithParser(parser: Parser | undefined): string | undefined {
   if (!parser) return 'no parser';
-  if (parser.type === 'map') return null;
+  if (parser.type === 'map') return;
 
   const { parseSpec } = parser;
   if (!parseSpec) return 'no parse spec';
@@ -324,7 +324,7 @@ export function issueWithParser(parser: Parser | undefined): string | null {
       if (!parseSpec['function']) return "must have a 'function'";
       break;
   }
-  return null;
+  return;
 }
 
 export function parseSpecHasFlatten(parseSpec: ParseSpec): boolean {
@@ -415,10 +415,12 @@ export function getTimestampSpecFormFields(timestampSpec: TimestampSpec) {
   }
 }
 
-export function issueWithTimestampSpec(timestampSpec: TimestampSpec | undefined): string | null {
+export function issueWithTimestampSpec(
+  timestampSpec: TimestampSpec | undefined,
+): string | undefined {
   if (!timestampSpec) return 'no spec';
   if (!timestampSpec.column && !timestampSpec.missingValue) return 'timestamp spec is blank';
-  return null;
+  return;
 }
 
 export interface DimensionsSpec {
@@ -722,15 +724,13 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
     type: 'string',
     suggestions: ['local', 'http', 'static-s3', 'static-google-blobstore'],
     info: (
-      <>
-        <p>
-          Druid connects to raw data through{' '}
-          <ExternalLink href="https://druid.apache.org/docs/latest/ingestion/firehose.html">
-            firehoses
-          </ExternalLink>
-          . You can change your selected firehose here.
-        </p>
-      </>
+      <p>
+        Druid connects to raw data through{' '}
+        <ExternalLink href="https://druid.apache.org/docs/latest/ingestion/firehose.html">
+          firehoses
+        </ExternalLink>
+        . You can change your selected firehose here.
+      </p>
     ),
   };
 
@@ -957,7 +957,7 @@ function nonEmptyArray(a: any) {
   return Array.isArray(a) && Boolean(a.length);
 }
 
-function issueWithFirehose(firehose: Firehose | undefined): string | null {
+function issueWithFirehose(firehose: Firehose | undefined): string | undefined {
   if (!firehose) return 'does not exist';
   if (!firehose.type) return 'missing a type';
   switch (firehose.type) {
@@ -984,10 +984,10 @@ function issueWithFirehose(firehose: Firehose | undefined): string | null {
       }
       break;
   }
-  return null;
+  return;
 }
 
-export function issueWithIoConfig(ioConfig: IoConfig | undefined): string | null {
+export function issueWithIoConfig(ioConfig: IoConfig | undefined): string | undefined {
   if (!ioConfig) return 'does not exist';
   if (!ioConfig.type) return 'missing a type';
   switch (ioConfig.type) {
@@ -1007,7 +1007,7 @@ export function issueWithIoConfig(ioConfig: IoConfig | undefined): string | null
       break;
   }
 
-  return null;
+  return;
 }
 
 export function getIoConfigTuningFormFields(
@@ -1296,13 +1296,14 @@ function filterIsFilename(filter: string): boolean {
   return !/[*?]/.test(filter);
 }
 
-function filenameFromPath(path: string): string | null {
+function filenameFromPath(path: string): string | undefined {
   const m = path.match(/([^\/.]+)[^\/]*?\/?$/);
-  return m ? m[1] : null;
+  if (!m) return;
+  return m[1];
 }
 
-function basenameFromFilename(filename: string): string | null {
-  return filename.split('.')[0] || null;
+function basenameFromFilename(filename: string): string | undefined {
+  return filename.split('.')[0];
 }
 
 export function fillDataSourceName(spec: IngestionSpec): IngestionSpec {
@@ -1313,12 +1314,12 @@ export function fillDataSourceName(spec: IngestionSpec): IngestionSpec {
   return deepSet(spec, 'dataSchema.dataSource', possibleName);
 }
 
-export function guessDataSourceName(ioConfig: IoConfig): string | null {
+export function guessDataSourceName(ioConfig: IoConfig): string | undefined {
   switch (ioConfig.type) {
     case 'index':
     case 'index_parallel':
       const firehose = ioConfig.firehose;
-      if (!firehose) return null;
+      if (!firehose) return;
 
       switch (firehose.type) {
         case 'local':
@@ -1327,27 +1328,27 @@ export function guessDataSourceName(ioConfig: IoConfig): string | null {
           } else if (firehose.baseDir) {
             return filenameFromPath(firehose.baseDir);
           } else {
-            return null;
+            return;
           }
 
         case 'static-s3':
           const s3Path = (firehose.uris || EMPTY_ARRAY)[0] || (firehose.prefixes || EMPTY_ARRAY)[0];
-          return s3Path ? filenameFromPath(s3Path) : null;
+          return s3Path ? filenameFromPath(s3Path) : undefined;
 
         case 'http':
-          return Array.isArray(firehose.uris) ? filenameFromPath(firehose.uris[0]) : null;
+          return Array.isArray(firehose.uris) ? filenameFromPath(firehose.uris[0]) : undefined;
       }
 
-      return null;
+      return;
 
     case 'kafka':
-      return ioConfig.topic || null;
+      return ioConfig.topic;
 
     case 'kinesis':
-      return ioConfig.stream || null;
+      return ioConfig.stream;
 
     default:
-      return null;
+      return;
   }
 }
 
@@ -1830,9 +1831,9 @@ export function fillParser(spec: IngestionSpec, sampleData: string[]): Ingestion
   return deepSet(spec, 'dataSchema.parser', { type: 'string', parseSpec });
 }
 
-function guessParseSpec(sampleData: string[]): ParseSpec | null {
+function guessParseSpec(sampleData: string[]): ParseSpec | undefined {
   const sampleDatum = sampleData[0];
-  if (!sampleDatum) return null;
+  if (!sampleDatum) return;
 
   if (sampleDatum.startsWith('{') && sampleDatum.endsWith('}')) {
     return parseSpecFromFormat('json');
@@ -1867,7 +1868,7 @@ export type DruidFilter = Record<string, any>;
 
 export interface DimensionFiltersWithRest {
   dimensionFilters: DruidFilter[];
-  restFilter: DruidFilter | null;
+  restFilter?: DruidFilter;
 }
 
 export function splitFilter(filter: DruidFilter | null): DimensionFiltersWithRest {
@@ -1887,16 +1888,18 @@ export function splitFilter(filter: DruidFilter | null): DimensionFiltersWithRes
       ? restFilters.length > 1
         ? { type: 'and', filters: restFilters }
         : restFilters[0]
-      : null,
+      : undefined,
   };
 }
 
-export function joinFilter(dimensionFiltersWithRest: DimensionFiltersWithRest): DruidFilter | null {
+export function joinFilter(
+  dimensionFiltersWithRest: DimensionFiltersWithRest,
+): DruidFilter | undefined {
   const { dimensionFilters, restFilter } = dimensionFiltersWithRest;
   let newFields = dimensionFilters || EMPTY_ARRAY;
   if (restFilter && restFilter.type) newFields = newFields.concat([restFilter]);
 
-  if (!newFields.length) return null;
+  if (!newFields.length) return;
   if (newFields.length === 1) return newFields[0];
   return { type: 'and', fields: newFields };
 }
diff --git a/web-console/src/utils/local-storage-keys.tsx b/web-console/src/utils/local-storage-keys.tsx
index 1aa50b7..620dc0e 100644
--- a/web-console/src/utils/local-storage-keys.tsx
+++ b/web-console/src/utils/local-storage-keys.tsx
@@ -43,7 +43,7 @@ export function localStorageSet(key: LocalStorageKeys, value: string): void {
   localStorage.setItem(key, value);
 }
 
-export function localStorageGet(key: LocalStorageKeys): string | null {
-  if (typeof localStorage === 'undefined') return null;
-  return localStorage.getItem(key);
+export function localStorageGet(key: LocalStorageKeys): string | undefined {
+  if (typeof localStorage === 'undefined') return;
+  return localStorage.getItem(key) || undefined;
 }
diff --git a/web-console/src/utils/query-manager.tsx b/web-console/src/utils/query-manager.tsx
index ecf78f1..d7f0acb 100644
--- a/web-console/src/utils/query-manager.tsx
+++ b/web-console/src/utils/query-manager.tsx
@@ -19,9 +19,9 @@
 import debounce from 'lodash.debounce';
 
 export interface QueryStateInt<R> {
-  result: R | null;
+  result?: R;
   loading: boolean;
-  error: string | null;
+  error?: string;
 }
 
 export interface QueryManagerOptions<Q, R> {
@@ -44,9 +44,7 @@ export class QueryManager<Q, R> {
   private lastIntermediateQuery: any;
   private actuallyLoading = false;
   private state: QueryStateInt<R> = {
-    result: null,
     loading: false,
-    error: null,
   };
   private currentQueryId = 0;
 
@@ -91,14 +89,14 @@ export class QueryManager<Q, R> {
         this.setState({
           result,
           loading: false,
-          error: null,
+          error: undefined,
         });
       },
       (e: Error) => {
         if (this.currentQueryId !== myQueryId) return;
         this.actuallyLoading = false;
         this.setState({
-          result: null,
+          result: undefined,
           loading: false,
           error: e.message,
         });
@@ -110,9 +108,9 @@ export class QueryManager<Q, R> {
     const currentActuallyLoading = this.actuallyLoading;
 
     this.setState({
-      result: null,
+      result: undefined,
       loading: true,
-      error: null,
+      error: undefined,
     });
 
     if (currentActuallyLoading) {
diff --git a/web-console/src/utils/query-state.ts b/web-console/src/utils/query-state.ts
index 8663ff0..60e09fa 100644
--- a/web-console/src/utils/query-state.ts
+++ b/web-console/src/utils/query-state.ts
@@ -22,8 +22,8 @@ export class QueryState<T> {
   static INIT: QueryState<any> = new QueryState({});
 
   public state: QueryStateState = 'init';
-  public error?: string | null;
-  public data?: T | null;
+  public error?: string;
+  public data?: T;
 
   constructor(opts: { loading?: boolean; error?: string; data?: T }) {
     if (opts.error) {
diff --git a/web-console/src/utils/rune-decoder.tsx b/web-console/src/utils/rune-decoder.tsx
index 77e9d06..2d82c0a 100644
--- a/web-console/src/utils/rune-decoder.tsx
+++ b/web-console/src/utils/rune-decoder.tsx
@@ -54,33 +54,29 @@ function processTimeseries(rune: any[]): HeaderRows {
 }
 
 function processArrayWithResultArray(rune: any[]): HeaderRows {
-  return flatArrayToHeaderRows([].concat(...rune.map(r => r.result)));
+  return flatArrayToHeaderRows(rune.flatMap(r => r.result));
 }
 
 function processArrayWithEvent(rune: any[]): HeaderRows {
-  return flatArrayToHeaderRows(rune.map((r: any) => r.event));
+  return flatArrayToHeaderRows(rune.map(r => r.event));
 }
 
 function processArrayWithResult(rune: any[]): HeaderRows {
-  return flatArrayToHeaderRows(rune.map((r: any) => r.result));
+  return flatArrayToHeaderRows(rune.map(r => r.result));
 }
 
 function processSelect(rune: any[]): HeaderRows {
-  return flatArrayToHeaderRows(
-    [].concat(...rune.map(r => r.result.events.map((e: any) => e.event))),
-  );
+  return flatArrayToHeaderRows(rune.flatMap(r => r.result.events.map((e: any) => e.event)));
 }
 
 function processScan(rune: any[]): HeaderRows {
   const header = rune[0].columns;
-  return flatArrayToHeaderRows([].concat(...rune.map(r => r.events)), header);
+  return flatArrayToHeaderRows(rune.flatMap(r => r.events), header);
 }
 
 function processSegmentMetadata(rune: any[]): HeaderRows {
-  const flatArray = ([] as any).concat(
-    ...rune.map(r =>
-      Object.keys(r.columns).map(k => Object.assign({ id: r.id, column: k }, r.columns[k])),
-    ),
+  const flatArray = rune.flatMap(r =>
+    Object.keys(r.columns).map(k => Object.assign({ id: r.id, column: k }, r.columns[k])),
   );
   return flatArrayToHeaderRows(flatArray);
 }
@@ -131,7 +127,7 @@ export function decodeRune(runeQuery: any, runeResult: any[]): HeaderRows {
       if (runeQuery.resultFormat === 'compactedList') {
         return {
           header: runeResult[0].columns,
-          rows: [].concat(...runeResult.map(r => r.events)),
+          rows: runeResult.flatMap(r => r.events),
         };
       }
       return processScan(runeResult);
diff --git a/web-console/src/utils/sampler.ts b/web-console/src/utils/sampler.ts
index 1009bb0..bb87bdd 100644
--- a/web-console/src/utils/sampler.ts
+++ b/web-console/src/utils/sampler.ts
@@ -19,7 +19,7 @@
 import axios from 'axios';
 
 import { getDruidErrorMessage } from './druid-query';
-import { alphanumericCompare, filterMap, sortWithPrefixSuffix } from './general';
+import { alphanumericCompare, sortWithPrefixSuffix } from './general';
 import {
   DimensionsSpec,
   getEmptyTimestampSpec,
@@ -98,11 +98,7 @@ export function headerFromSampleResponse(
   columnOrder?: string[],
 ): string[] {
   let columns = sortWithPrefixSuffix(
-    dedupe(
-      [].concat(
-        ...(filterMap(sampleResponse.data, s => (s.parsed ? Object.keys(s.parsed) : null)) as any),
-      ),
-    ).sort(),
+    dedupe(sampleResponse.data.flatMap(s => (s.parsed ? Object.keys(s.parsed) : []))).sort(),
     columnOrder || ['__time'],
     [],
     alphanumericCompare,
diff --git a/web-console/src/views/datasource-view/datasource-view.tsx b/web-console/src/views/datasource-view/datasource-view.tsx
index 8297a71..6580af6 100644
--- a/web-console/src/views/datasource-view/datasource-view.tsx
+++ b/web-console/src/views/datasource-view/datasource-view.tsx
@@ -110,16 +110,16 @@ export interface DatasourcesViewState {
   datasources: Datasource[] | null;
   tiers: string[];
   defaultRules: any[];
-  datasourcesError: string | null;
+  datasourcesError?: string;
   datasourcesFilter: Filter[];
 
   showDisabled: boolean;
-  retentionDialogOpenOn: { datasource: string; rules: any[] } | null;
-  compactionDialogOpenOn: { datasource: string; configData: any } | null;
-  dropDataDatasource: string | null;
-  enableDatasource: string | null;
-  killDatasource: string | null;
-  dropReloadDatasource: string | null;
+  retentionDialogOpenOn?: { datasource: string; rules: any[] };
+  compactionDialogOpenOn?: { datasource: string; configData: any };
+  dropDataDatasource?: string;
+  enableDatasource?: string;
+  killDatasource?: string;
+  dropReloadDatasource?: string;
   dropReloadAction: 'drop' | 'reload';
   dropReloadInterval: string;
   hiddenColumns: LocalStorageBackedArray<string>;
@@ -167,16 +167,9 @@ GROUP BY 1`;
       datasources: null,
       tiers: [],
       defaultRules: [],
-      datasourcesError: null,
       datasourcesFilter: [],
 
       showDisabled: false,
-      retentionDialogOpenOn: null,
-      compactionDialogOpenOn: null,
-      dropDataDatasource: null,
-      enableDatasource: null,
-      killDatasource: null,
-      dropReloadDatasource: null,
       dropReloadAction: 'drop',
       dropReloadInterval: '',
       hiddenColumns: new LocalStorageBackedArray<string>(
@@ -253,7 +246,7 @@ GROUP BY 1`;
           datasources: result ? result.datasources : null,
           tiers: result ? result.tiers : [],
           defaultRules: result ? result.defaultRules : [],
-          datasourcesError: error,
+          datasourcesError: error || undefined,
         });
       },
     });
@@ -286,7 +279,7 @@ GROUP BY 1`;
         failText="Could not drop data"
         intent={Intent.DANGER}
         onClose={() => {
-          this.setState({ dropDataDatasource: null });
+          this.setState({ dropDataDatasource: undefined });
         }}
         onSuccess={() => {
           this.datasourceQueryManager.rerunLastQuery();
@@ -317,7 +310,7 @@ GROUP BY 1`;
         failText="Could not enable datasource"
         intent={Intent.PRIMARY}
         onClose={() => {
-          this.setState({ enableDatasource: null });
+          this.setState({ enableDatasource: undefined });
         }}
         onSuccess={() => {
           this.datasourceQueryManager.rerunLastQuery();
@@ -353,7 +346,7 @@ GROUP BY 1`;
         failText={`Could not ${isDrop ? 'drop' : 'reload'} data`}
         intent={Intent.PRIMARY}
         onClose={() => {
-          this.setState({ dropReloadDatasource: null });
+          this.setState({ dropReloadDatasource: undefined });
         }}
         onSuccess={() => {
           this.datasourceQueryManager.rerunLastQuery();
@@ -392,7 +385,7 @@ GROUP BY 1`;
         failText="Could not submit kill task"
         intent={Intent.DANGER}
         onClose={() => {
-          this.setState({ killDatasource: null });
+          this.setState({ killDatasource: undefined });
         }}
         onSuccess={() => {
           this.datasourceQueryManager.rerunLastQuery();
@@ -433,7 +426,7 @@ GROUP BY 1`;
     const { datasources, defaultRules } = this.state;
     if (!datasources) return;
 
-    this.setState({ retentionDialogOpenOn: null });
+    this.setState({ retentionDialogOpenOn: undefined });
     setTimeout(() => {
       this.setState({
         retentionDialogOpenOn: {
@@ -445,10 +438,10 @@ GROUP BY 1`;
   };
 
   private saveCompaction = async (compactionConfig: any) => {
-    if (compactionConfig === null) return;
+    if (!compactionConfig) return;
     try {
       await axios.post(`/druid/coordinator/v1/config/compaction`, compactionConfig);
-      this.setState({ compactionDialogOpenOn: null });
+      this.setState({ compactionDialogOpenOn: undefined });
       this.datasourceQueryManager.rerunLastQuery();
     } catch (e) {
       AppToaster.show({
@@ -460,7 +453,7 @@ GROUP BY 1`;
 
   private deleteCompaction = async () => {
     const { compactionDialogOpenOn } = this.state;
-    if (compactionDialogOpenOn === null) return;
+    if (!compactionDialogOpenOn) return;
     const datasource = compactionDialogOpenOn.datasource;
     AppToaster.show({
       message: `Are you sure you want to delete ${datasource}'s compaction?`,
@@ -470,7 +463,7 @@ GROUP BY 1`;
         onClick: async () => {
           try {
             await axios.delete(`/druid/coordinator/v1/config/compaction/${datasource}`);
-            this.setState({ compactionDialogOpenOn: null }, () =>
+            this.setState({ compactionDialogOpenOn: undefined }, () =>
               this.datasourceQueryManager.rerunLastQuery(),
             );
           } catch (e) {
@@ -547,7 +540,7 @@ GROUP BY 1`;
         rules={retentionDialogOpenOn.rules}
         tiers={tiers}
         onEditDefaults={this.editDefaultRules}
-        onCancel={() => this.setState({ retentionDialogOpenOn: null })}
+        onCancel={() => this.setState({ retentionDialogOpenOn: undefined })}
         onSave={this.saveRules}
       />
     );
@@ -562,7 +555,7 @@ GROUP BY 1`;
       <CompactionDialog
         datasource={compactionDialogOpenOn.datasource}
         configData={compactionDialogOpenOn.configData}
-        onClose={() => this.setState({ compactionDialogOpenOn: null })}
+        onClose={() => this.setState({ compactionDialogOpenOn: undefined })}
         onSave={this.saveCompaction}
         onDelete={this.deleteCompaction}
       />
@@ -808,7 +801,7 @@ GROUP BY 1`;
     );
   }
 
-  render() {
+  render(): JSX.Element {
     const { goToQuery, noSqlMode } = this.props;
     const { showDisabled, hiddenColumns } = this.state;
 
diff --git a/web-console/src/views/home-view/__snapshots__/home-view.spec.tsx.snap b/web-console/src/views/home-view/__snapshots__/home-view.spec.tsx.snap
index 739c15e..37b29d2 100644
--- a/web-console/src/views/home-view/__snapshots__/home-view.spec.tsx.snap
+++ b/web-console/src/views/home-view/__snapshots__/home-view.spec.tsx.snap
@@ -9,7 +9,7 @@ exports[`home view matches snapshot 1`] = `
     target="_blank"
   >
     <Blueprint3.Card
-      className="status-card"
+      className="home-view-card"
       elevation={0}
       interactive={true}
     >
@@ -30,7 +30,7 @@ exports[`home view matches snapshot 1`] = `
     href="#datasources"
   >
     <Blueprint3.Card
-      className="status-card"
+      className="home-view-card"
       elevation={0}
       interactive={true}
     >
@@ -51,7 +51,7 @@ exports[`home view matches snapshot 1`] = `
     href="#segments"
   >
     <Blueprint3.Card
-      className="status-card"
+      className="home-view-card"
       elevation={0}
       interactive={true}
     >
@@ -72,7 +72,7 @@ exports[`home view matches snapshot 1`] = `
     href="#tasks"
   >
     <Blueprint3.Card
-      className="status-card"
+      className="home-view-card"
       elevation={0}
       interactive={true}
     >
@@ -93,7 +93,7 @@ exports[`home view matches snapshot 1`] = `
     href="#tasks"
   >
     <Blueprint3.Card
-      className="status-card"
+      className="home-view-card"
       elevation={0}
       interactive={true}
     >
@@ -114,7 +114,7 @@ exports[`home view matches snapshot 1`] = `
     href="#servers"
   >
     <Blueprint3.Card
-      className="status-card"
+      className="home-view-card"
       elevation={0}
       interactive={true}
     >
@@ -135,7 +135,7 @@ exports[`home view matches snapshot 1`] = `
     href="#lookups"
   >
     <Blueprint3.Card
-      className="status-card"
+      className="home-view-card"
       elevation={0}
       interactive={true}
     >
diff --git a/web-console/src/views/home-view/home-view.scss b/web-console/src/views/home-view/home-view.scss
index 077a5b9..c2f5bb1 100644
--- a/web-console/src/views/home-view/home-view.scss
+++ b/web-console/src/views/home-view/home-view.scss
@@ -28,7 +28,7 @@
     color: inherit;
   }
 
-  .status-card {
-    height: 160px;
+  .home-view-card {
+    height: 170px;
   }
 }
diff --git a/web-console/src/views/home-view/home-view.tsx b/web-console/src/views/home-view/home-view.tsx
index 6090fe7..38eff67 100644
--- a/web-console/src/views/home-view/home-view.tsx
+++ b/web-console/src/views/home-view/home-view.tsx
@@ -34,7 +34,7 @@ export interface CardOptions {
   title: string;
   loading?: boolean;
   content: JSX.Element | string;
-  error?: string | null;
+  error?: string;
 }
 
 export interface HomeViewProps {
@@ -42,28 +42,23 @@ export interface HomeViewProps {
 }
 
 export interface HomeViewState {
-  statusLoading: boolean;
-  status: any;
-  statusError: string | null;
+  versionLoading: boolean;
+  version: string;
+  versionError?: string;
 
   datasourceCountLoading: boolean;
   datasourceCount: number;
-  datasourceCountError: string | null;
+  datasourceCountError?: string;
 
   segmentCountLoading: boolean;
   segmentCount: number;
   unavailableSegmentCount: number;
-  segmentCountError: string | null;
+  segmentCountError?: string;
 
   supervisorCountLoading: boolean;
   runningSupervisorCount: number;
   suspendedSupervisorCount: number;
-  supervisorCountError: string | null;
-
-  lookupsCountLoading: boolean;
-  lookupsCount: number;
-  lookupsCountError: string | null;
-  lookupsUninitialized: boolean;
+  supervisorCountError?: string;
 
   taskCountLoading: boolean;
   runningTaskCount: number;
@@ -71,7 +66,7 @@ export interface HomeViewState {
   successTaskCount: number;
   failedTaskCount: number;
   waitingTaskCount: number;
-  taskCountError: string | null;
+  taskCountError?: string;
 
   serverCountLoading: boolean;
   coordinatorCount: number;
@@ -81,11 +76,16 @@ export interface HomeViewState {
   historicalCount: number;
   middleManagerCount: number;
   peonCount: number;
-  serverCountError: string | null;
+  serverCountError?: string;
+
+  lookupsCountLoading: boolean;
+  lookupsCount: number;
+  lookupsUninitialized: boolean;
+  lookupsCountError?: string;
 }
 
 export class HomeView extends React.PureComponent<HomeViewProps, HomeViewState> {
-  private statusQueryManager: QueryManager<null, any>;
+  private versionQueryManager: QueryManager<null, string>;
   private datasourceQueryManager: QueryManager<boolean, any>;
   private segmentQueryManager: QueryManager<boolean, any>;
   private supervisorQueryManager: QueryManager<null, any>;
@@ -96,28 +96,19 @@ export class HomeView extends React.PureComponent<HomeViewProps, HomeViewState>
   constructor(props: HomeViewProps, context: any) {
     super(props, context);
     this.state = {
-      statusLoading: true,
-      status: null,
-      statusError: null,
+      versionLoading: true,
+      version: '',
 
       datasourceCountLoading: false,
       datasourceCount: 0,
-      datasourceCountError: null,
 
       segmentCountLoading: false,
       segmentCount: 0,
       unavailableSegmentCount: 0,
-      segmentCountError: null,
 
       supervisorCountLoading: false,
       runningSupervisorCount: 0,
       suspendedSupervisorCount: 0,
-      supervisorCountError: null,
-
-      lookupsCountLoading: false,
-      lookupsCount: 0,
-      lookupsCountError: null,
-      lookupsUninitialized: false,
 
       taskCountLoading: false,
       runningTaskCount: 0,
@@ -125,7 +116,6 @@ export class HomeView extends React.PureComponent<HomeViewProps, HomeViewState>
       successTaskCount: 0,
       failedTaskCount: 0,
       waitingTaskCount: 0,
-      taskCountError: null,
 
       serverCountLoading: false,
       coordinatorCount: 0,
@@ -135,19 +125,22 @@ export class HomeView extends React.PureComponent<HomeViewProps, HomeViewState>
       historicalCount: 0,
       middleManagerCount: 0,
       peonCount: 0,
-      serverCountError: null,
+
+      lookupsCountLoading: false,
+      lookupsCount: 0,
+      lookupsUninitialized: false,
     };
 
-    this.statusQueryManager = new QueryManager({
+    this.versionQueryManager = new QueryManager({
       processQuery: async () => {
         const statusResp = await axios.get('/status');
-        return statusResp.data;
+        return statusResp.data.version;
       },
       onStateChange: ({ result, loading, error }) => {
         this.setState({
-          statusLoading: loading,
-          status: result,
-          statusError: error,
+          versionLoading: loading,
+          version: result,
+          versionError: error,
         });
       },
     });
@@ -169,7 +162,7 @@ export class HomeView extends React.PureComponent<HomeViewProps, HomeViewState>
         this.setState({
           datasourceCountLoading: loading,
           datasourceCount: result,
-          datasourceCountError: error,
+          datasourceCountError: error || undefined,
         });
       },
     });
@@ -309,7 +302,7 @@ GROUP BY 1`,
       processQuery: async () => {
         const resp = await axios.get('/druid/coordinator/v1/lookups/status');
         const data = resp.data;
-        const lookupsCount = Object.keys(data.__default).length;
+        const lookupsCount = sum(Object.keys(data).map(k => Object.keys(data[k]).length));
         return {
           lookupsCount,
         };
@@ -317,9 +310,9 @@ GROUP BY 1`,
       onStateChange: ({ result, loading, error }) => {
         this.setState({
           lookupsCount: result ? result.lookupsCount : 0,
+          lookupsUninitialized: error === 'Request failed with status code 404',
           lookupsCountLoading: loading,
           lookupsCountError: error,
-          lookupsUninitialized: error === 'Request failed with status code 404',
         });
       },
     });
@@ -328,7 +321,7 @@ GROUP BY 1`,
   componentDidMount(): void {
     const { noSqlMode } = this.props;
 
-    this.statusQueryManager.runQuery(null);
+    this.versionQueryManager.runQuery(null);
     this.datasourceQueryManager.runQuery(noSqlMode);
     this.segmentQueryManager.runQuery(noSqlMode);
     this.supervisorQueryManager.runQuery(null);
@@ -338,7 +331,7 @@ GROUP BY 1`,
   }
 
   componentWillUnmount(): void {
-    this.statusQueryManager.terminate();
+    this.versionQueryManager.terminate();
     this.datasourceQueryManager.terminate();
     this.segmentQueryManager.terminate();
     this.supervisorQueryManager.terminate();
@@ -349,7 +342,7 @@ GROUP BY 1`,
   renderCard(cardOptions: CardOptions): JSX.Element {
     return (
       <a href={cardOptions.href} target={cardOptions.href[0] === '/' ? '_blank' : undefined}>
-        <Card className="status-card" interactive>
+        <Card className="home-view-card" interactive>
           <H5>
             <Icon color="#bfccd5" icon={cardOptions.icon} />
             &nbsp;{cardOptions.title}
@@ -366,7 +359,7 @@ GROUP BY 1`,
     );
   }
 
-  render() {
+  render(): JSX.Element {
     const state = this.state;
 
     return (
@@ -375,9 +368,9 @@ GROUP BY 1`,
           href: UrlBaser.base('/status'),
           icon: IconNames.GRAPH,
           title: 'Status',
-          loading: state.statusLoading,
-          content: state.status ? `Apache Druid is running version ${state.status.version}` : '',
-          error: state.statusError,
+          loading: state.versionLoading,
+          content: state.version ? `Apache Druid is running version ${state.version}` : '',
+          error: state.versionError,
         })}
         {this.renderCard({
           href: '#datasources',
@@ -493,7 +486,7 @@ GROUP BY 1`,
               </p>
             </>
           ),
-          error: !state.lookupsUninitialized ? state.lookupsCountError : null,
+          error: !state.lookupsUninitialized ? state.lookupsCountError : undefined,
         })}
       </div>
     );
diff --git a/web-console/src/views/load-data-view/filter-table/filter-table.spec.tsx b/web-console/src/views/load-data-view/filter-table/filter-table.spec.tsx
index 935ebfd..a08fff6 100644
--- a/web-console/src/views/load-data-view/filter-table/filter-table.spec.tsx
+++ b/web-console/src/views/load-data-view/filter-table/filter-table.spec.tsx
@@ -39,8 +39,8 @@ describe('filter table', () => {
         columnFilter=""
         dimensionFilters={[]}
         selectedFilterIndex={-1}
-        onShowGlobalFilter={() => null}
-        onFilterSelect={() => null}
+        onShowGlobalFilter={() => {}}
+        onFilterSelect={() => {}}
       />
     );
 
diff --git a/web-console/src/views/load-data-view/filter-table/filter-table.tsx b/web-console/src/views/load-data-view/filter-table/filter-table.tsx
index 5fde4f8..d7da946 100644
--- a/web-console/src/views/load-data-view/filter-table/filter-table.tsx
+++ b/web-console/src/views/load-data-view/filter-table/filter-table.tsx
@@ -37,7 +37,7 @@ export interface FilterTableProps {
 }
 
 export class FilterTable extends React.PureComponent<FilterTableProps> {
-  render() {
+  render(): JSX.Element {
     const {
       sampleData,
       columnFilter,
diff --git a/web-console/src/views/load-data-view/load-data-view.scss b/web-console/src/views/load-data-view/load-data-view.scss
index 583ae58..003b1dc 100644
--- a/web-console/src/views/load-data-view/load-data-view.scss
+++ b/web-console/src/views/load-data-view/load-data-view.scss
@@ -30,20 +30,27 @@
     'main next';
 
   &.load-data-continue-view {
-    display: grid;
-    justify-content: center;
-    grid-gap: $standard-padding;
-    grid-template-columns: 500px 500px;
+    display: block;
+    text-align: center;
 
     .spec-card {
+      display: inline-block;
+      width: 480px;
       height: 100px;
-      display: grid;
-      grid-gap: $standard-padding;
-      grid-template-columns: 30px 1fr;
+      margin: 10px;
+      text-align: left;
+
+      .spec-card-icon {
+        vertical-align: top;
+      }
 
       .spec-card-header {
         font-size: 25px;
         line-height: 30px;
+        display: inline-block;
+        vertical-align: top;
+        padding-left: 10px;
+
         .spec-card-caption {
           font-size: 15px;
           line-height: 20px;
diff --git a/web-console/src/views/load-data-view/load-data-view.tsx b/web-console/src/views/load-data-view/load-data-view.tsx
index 68e0815..f3de329 100644
--- a/web-console/src/views/load-data-view/load-data-view.tsx
+++ b/web-console/src/views/load-data-view/load-data-view.tsx
@@ -152,18 +152,16 @@ function showRawLine(line: string): string {
 function getTimestampSpec(headerAndRows: HeaderAndRows | null): TimestampSpec {
   if (!headerAndRows) return getEmptyTimestampSpec();
 
-  const timestampSpecs = headerAndRows.header
-    .map(sampleHeader => {
-      const possibleFormat = possibleDruidFormatForValues(
-        filterMap(headerAndRows.rows, d => (d.parsed ? d.parsed[sampleHeader] : null)),
-      );
-      if (!possibleFormat) return null;
-      return {
-        column: sampleHeader,
-        format: possibleFormat,
-      };
-    })
-    .filter(Boolean);
+  const timestampSpecs = filterMap(headerAndRows.header, sampleHeader => {
+    const possibleFormat = possibleDruidFormatForValues(
+      filterMap(headerAndRows.rows, d => (d.parsed ? d.parsed[sampleHeader] : undefined)),
+    );
+    if (!possibleFormat) return;
+    return {
+      column: sampleHeader,
+      format: possibleFormat,
+    };
+  });
 
   return timestampSpecs[0] || getEmptyTimestampSpec();
 }
@@ -220,25 +218,25 @@ const VIEW_TITLE: Record<Step, string> = {
 };
 
 export interface LoadDataViewProps {
-  initSupervisorId?: string | null;
-  initTaskId?: string | null;
+  initSupervisorId?: string;
+  initTaskId?: string;
   goToTask: (taskId: string | undefined, supervisor?: string) => void;
 }
 
 export interface LoadDataViewState {
   step: Step;
   spec: IngestionSpec;
-  cacheKey: string | undefined;
+  cacheKey?: string;
   // dialogs / modals
   showResetConfirm: boolean;
-  newRollup: boolean | null;
-  newDimensionMode: DimensionMode | null;
+  newRollup?: boolean;
+  newDimensionMode?: DimensionMode;
   showViewValueModal: boolean;
   str: string;
 
   // welcome
-  overlordModules: string[] | null;
-  selectedComboType: IngestionComboTypeWithExtra | null;
+  overlordModules?: string[];
+  selectedComboType?: IngestionComboTypeWithExtra;
 
   // general
   sampleStrategy: SampleStrategy;
@@ -254,7 +252,7 @@ export interface LoadDataViewState {
   // for flatten
   flattenQueryState: QueryState<HeaderAndRows>;
   selectedFlattenFieldIndex: number;
-  selectedFlattenField: FlattenField | null;
+  selectedFlattenField?: FlattenField;
 
   // for timestamp
   timestampQueryState: QueryState<{
@@ -265,12 +263,12 @@ export interface LoadDataViewState {
   // for transform
   transformQueryState: QueryState<HeaderAndRows>;
   selectedTransformIndex: number;
-  selectedTransform: Transform | null;
+  selectedTransform?: Transform;
 
   // for filter
   filterQueryState: QueryState<HeaderAndRows>;
   selectedFilterIndex: number;
-  selectedFilter: DruidFilter | null;
+  selectedFilter?: DruidFilter;
   showGlobalFilter: boolean;
 
   // for schema
@@ -280,9 +278,9 @@ export interface LoadDataViewState {
     metricsSpec: MetricSpec[];
   }>;
   selectedDimensionSpecIndex: number;
-  selectedDimensionSpec: DimensionSpec | null;
+  selectedDimensionSpec?: DimensionSpec;
   selectedMetricSpecIndex: number;
-  selectedMetricSpec: MetricSpec | null;
+  selectedMetricSpec?: MetricSpec;
 
   continueToSpec: boolean;
 }
@@ -296,19 +294,12 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
     this.state = {
       step: 'welcome',
       spec,
-      cacheKey: undefined,
 
       // dialogs / modals
       showResetConfirm: false,
       showViewValueModal: false,
-      newRollup: null,
-      newDimensionMode: null,
       str: '',
 
-      // welcome
-      overlordModules: null,
-      selectedComboType: null,
-
       // general
       sampleStrategy: 'start',
       columnFilter: '',
@@ -323,7 +314,6 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
       // for flatten
       flattenQueryState: QueryState.INIT,
       selectedFlattenFieldIndex: -1,
-      selectedFlattenField: null,
 
       // for timestamp
       timestampQueryState: QueryState.INIT,
@@ -331,20 +321,16 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
       // for transform
       transformQueryState: QueryState.INIT,
       selectedTransformIndex: -1,
-      selectedTransform: null,
 
       // for filter
       filterQueryState: QueryState.INIT,
       selectedFilterIndex: -1,
-      selectedFilter: null,
       showGlobalFilter: false,
 
       // for dimensions
       schemaQueryState: QueryState.INIT,
       selectedDimensionSpecIndex: -1,
-      selectedDimensionSpec: null,
       selectedMetricSpecIndex: -1,
-      selectedMetricSpec: null,
 
       continueToSpec: false,
     };
@@ -415,16 +401,16 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
     localStorageSet(LocalStorageKeys.INGESTION_SPEC, JSON.stringify(newSpec));
   };
 
-  render() {
+  render(): JSX.Element {
     const { step, continueToSpec } = this.state;
     if (!continueToSpec) {
       return (
         <div className={classNames('load-data-continue-view load-data-view')}>
-          <Card className={'spec-card'} interactive onClick={this.handleResetConfirm}>
-            <Icon iconSize={30} icon={IconNames.ASTERISK} />
+          <Card className={'spec-card'} interactive onClick={this.handleResetSpec}>
+            <Icon className="spec-card-icon" icon={IconNames.ASTERISK} iconSize={30} />
             <div className={'spec-card-header'}>
               Start a new spec
-              <div className={'spec-card-caption'}>start a new spec something something</div>
+              <div className={'spec-card-caption'}>Start a new ingestion flow</div>
             </div>
           </Card>
           <Card
@@ -432,7 +418,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
             interactive
             onClick={() => this.setState({ continueToSpec: true })}
           >
-            <Icon icon={IconNames.REPEAT} iconSize={30} />
+            <Icon className="spec-card-icon" icon={IconNames.REPEAT} iconSize={30} />
             <div className={'spec-card-header'}>
               Continue from previous spec
               <div className={'spec-card-caption'}>
@@ -444,6 +430,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
         </div>
       );
     }
+
     return (
       <div className={classNames('load-data-view', 'app-view', step)}>
         {this.renderStepNav()}
@@ -542,7 +529,9 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
         className={classNames({ disabled: !goodToGo, active: selectedComboType === comboType })}
         interactive
         onClick={() => {
-          this.setState({ selectedComboType: selectedComboType !== comboType ? comboType : null });
+          this.setState({
+            selectedComboType: selectedComboType !== comboType ? comboType : undefined,
+          });
         }}
       >
         <img src={UrlBaser.base(`/assets/${getIngestionImage(comboType)}.png`)} />
@@ -765,6 +754,11 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
     this.setState({ showResetConfirm: true });
   };
 
+  private handleResetSpec = () => {
+    this.setState({ showResetConfirm: false, step: 'welcome', continueToSpec: true });
+    this.updateSpec({} as any);
+  };
+
   renderResetConfirm() {
     const { showResetConfirm } = this.state;
     if (!showResetConfirm) return null;
@@ -777,10 +771,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
         intent={Intent.DANGER}
         isOpen
         onCancel={() => this.setState({ showResetConfirm: false })}
-        onConfirm={() => {
-          this.setState({ showResetConfirm: false, step: 'welcome', continueToSpec: true });
-          this.updateSpec({} as any);
-        }}
+        onConfirm={this.handleResetSpec}
       >
         <p>This will discard the current progress in the spec.</p>
       </Alert>
@@ -934,7 +925,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
     const parser: Parser = deepGet(spec, 'dataSchema.parser') || EMPTY_OBJECT;
     const parserColumns: string[] = deepGet(parser, 'parseSpec.columns') || [];
 
-    let issue: string | null = null;
+    let issue: string | undefined;
     if (issueWithIoConfig(ioConfig)) {
       issue = `IoConfig not ready, ${issueWithIoConfig(ioConfig)}`;
     } else if (issueWithParser(parser)) {
@@ -1124,7 +1115,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
     const close = () => {
       this.setState({
         selectedFlattenFieldIndex: -1,
-        selectedFlattenField: null,
+        selectedFlattenField: undefined,
       });
     };
 
@@ -1211,7 +1202,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
     const timestampSpec =
       deepGet(spec, 'dataSchema.parser.parseSpec.timestampSpec') || EMPTY_OBJECT;
 
-    let issue: string | null = null;
+    let issue: string | undefined;
     if (issueWithIoConfig(ioConfig)) {
       issue = `IoConfig not ready, ${issueWithIoConfig(ioConfig)}`;
     } else if (issueWithParser(parser)) {
@@ -1380,7 +1371,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
     const parser: Parser = deepGet(spec, 'dataSchema.parser') || EMPTY_OBJECT;
     const parserColumns: string[] = deepGet(parser, 'parseSpec.columns') || [];
 
-    let issue: string | null = null;
+    let issue: string | undefined;
     if (issueWithIoConfig(ioConfig)) {
       issue = `IoConfig not ready, ${issueWithIoConfig(ioConfig)}`;
     } else if (issueWithParser(parser)) {
@@ -1525,7 +1516,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
     const close = () => {
       this.setState({
         selectedTransformIndex: -1,
-        selectedTransform: null,
+        selectedTransform: undefined,
       });
     };
 
@@ -1604,7 +1595,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
     const parser: Parser = deepGet(spec, 'dataSchema.parser') || EMPTY_OBJECT;
     const parserColumns: string[] = deepGet(parser, 'parseSpec.columns') || [];
 
-    let issue: string | null = null;
+    let issue: string | undefined;
     if (issueWithIoConfig(ioConfig)) {
       issue = `IoConfig not ready, ${issueWithIoConfig(ioConfig)}`;
     } else if (issueWithParser(parser)) {
@@ -1765,7 +1756,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
     const close = () => {
       this.setState({
         selectedFilterIndex: -1,
-        selectedFilter: null,
+        selectedFilter: undefined,
       });
     };
 
@@ -1909,7 +1900,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
     const dimensionsSpec: DimensionsSpec =
       deepGet(spec, 'dataSchema.parser.parseSpec.dimensionsSpec') || EMPTY_OBJECT;
 
-    let issue: string | null = null;
+    let issue: string | undefined;
     if (issueWithIoConfig(ioConfig)) {
       issue = `IoConfig not ready, ${issueWithIoConfig(ioConfig)}`;
     } else if (issueWithParser(parser)) {
@@ -2134,9 +2125,9 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
   }
 
   private onDimensionOrMetricSelect = (
-    selectedDimensionSpec: DimensionSpec | null,
+    selectedDimensionSpec: DimensionSpec | undefined,
     selectedDimensionSpecIndex: number,
-    selectedMetricSpec: MetricSpec | null,
+    selectedMetricSpec: MetricSpec | undefined,
     selectedMetricSpecIndex: number,
   ) => {
     this.setState({
@@ -2149,7 +2140,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
 
   renderChangeRollupAction() {
     const { newRollup, spec, sampleStrategy, cacheKey } = this.state;
-    if (newRollup === null) return;
+    if (typeof newRollup === 'undefined') return;
 
     return (
       <AsyncActionDialog
@@ -2171,7 +2162,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
         successText={`Rollup was ${newRollup ? 'enabled' : 'disabled'}. Schema has been updated.`}
         failText="Could change rollup"
         intent={Intent.WARNING}
-        onClose={() => this.setState({ newRollup: null })}
+        onClose={() => this.setState({ newRollup: undefined })}
       >
         <p>{`Are you sure you want to ${newRollup ? 'enable' : 'disable'} rollup?`}</p>
         <p>Making this change will reset any work you have done in this section.</p>
@@ -2181,7 +2172,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
 
   renderChangeDimensionModeAction() {
     const { newDimensionMode, spec, sampleStrategy, cacheKey } = this.state;
-    if (newDimensionMode === null) return;
+    if (typeof newDimensionMode === 'undefined') return;
     const autoDetect = newDimensionMode === 'auto-detect';
 
     return (
@@ -2206,7 +2197,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
         }. Schema has been updated.`}
         failText="Could change dimension mode"
         intent={Intent.WARNING}
-        onClose={() => this.setState({ newDimensionMode: null })}
+        onClose={() => this.setState({ newDimensionMode: undefined })}
       >
         <p>
           {autoDetect
@@ -2224,7 +2215,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
     const close = () => {
       this.setState({
         selectedDimensionSpecIndex: -1,
-        selectedDimensionSpec: null,
+        selectedDimensionSpec: undefined,
       });
     };
 
@@ -2310,7 +2301,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
     const close = () => {
       this.setState({
         selectedMetricSpecIndex: -1,
-        selectedMetricSpec: null,
+        selectedMetricSpec: undefined,
       });
     };
 
diff --git a/web-console/src/views/load-data-view/parse-data-table/parse-data-table.spec.tsx b/web-console/src/views/load-data-view/parse-data-table/parse-data-table.spec.tsx
index 753dabb..92da603 100644
--- a/web-console/src/views/load-data-view/parse-data-table/parse-data-table.spec.tsx
+++ b/web-console/src/views/load-data-view/parse-data-table/parse-data-table.spec.tsx
@@ -35,13 +35,13 @@ describe('parse data table', () => {
 
     const parseDataTable = (
       <ParseDataTable
-        openModal={() => null}
+        openModal={() => {}}
         sampleData={sampleData}
         columnFilter=""
         canFlatten={false}
         flattenedColumnsOnly={false}
         flattenFields={[]}
-        onFlattenFieldSelect={() => null}
+        onFlattenFieldSelect={() => {}}
       />
     );
 
diff --git a/web-console/src/views/load-data-view/parse-data-table/parse-data-table.tsx b/web-console/src/views/load-data-view/parse-data-table/parse-data-table.tsx
index 9a47216..2d00968 100644
--- a/web-console/src/views/load-data-view/parse-data-table/parse-data-table.tsx
+++ b/web-console/src/views/load-data-view/parse-data-table/parse-data-table.tsx
@@ -38,7 +38,7 @@ export interface ParseDataTableProps {
 }
 
 export class ParseDataTable extends React.PureComponent<ParseDataTableProps> {
-  render() {
+  render(): JSX.Element {
     const {
       sampleData,
       columnFilter,
diff --git a/web-console/src/views/load-data-view/parse-time-table/parse-time-table.spec.tsx b/web-console/src/views/load-data-view/parse-time-table/parse-time-table.spec.tsx
index ea5aa5a..4982d7e 100644
--- a/web-console/src/views/load-data-view/parse-time-table/parse-time-table.spec.tsx
+++ b/web-console/src/views/load-data-view/parse-time-table/parse-time-table.spec.tsx
@@ -43,7 +43,7 @@ describe('parse time table', () => {
         }}
         columnFilter=""
         possibleTimestampColumnsOnly={false}
-        onTimestampColumnSelect={() => null}
+        onTimestampColumnSelect={() => {}}
       />
     );
 
diff --git a/web-console/src/views/load-data-view/parse-time-table/parse-time-table.tsx b/web-console/src/views/load-data-view/parse-time-table/parse-time-table.tsx
index f2a79ca..ae1b16b 100644
--- a/web-console/src/views/load-data-view/parse-time-table/parse-time-table.tsx
+++ b/web-console/src/views/load-data-view/parse-time-table/parse-time-table.tsx
@@ -43,7 +43,7 @@ export interface ParseTimeTableProps {
 }
 
 export class ParseTimeTable extends React.PureComponent<ParseTimeTableProps> {
-  render() {
+  render(): JSX.Element {
     const {
       sampleBundle,
       columnFilter,
@@ -67,7 +67,7 @@ export class ParseTimeTable extends React.PureComponent<ParseTimeTableProps> {
             const possibleFormat = timestamp
               ? null
               : possibleDruidFormatForValues(
-                  filterMap(headerAndRows.rows, d => (d.parsed ? d.parsed[columnName] : null)),
+                  filterMap(headerAndRows.rows, d => (d.parsed ? d.parsed[columnName] : undefined)),
                 );
             if (possibleTimestampColumnsOnly && !timestamp && !possibleFormat) return;
 
diff --git a/web-console/src/views/load-data-view/schema-table/schema-table.spec.tsx b/web-console/src/views/load-data-view/schema-table/schema-table.spec.tsx
index fd530a4..9695696 100644
--- a/web-console/src/views/load-data-view/schema-table/schema-table.spec.tsx
+++ b/web-console/src/views/load-data-view/schema-table/schema-table.spec.tsx
@@ -43,7 +43,7 @@ describe('schema table', () => {
         columnFilter=""
         selectedDimensionSpecIndex={-1}
         selectedMetricSpecIndex={-1}
-        onDimensionOrMetricSelect={() => null}
+        onDimensionOrMetricSelect={() => {}}
       />
     );
 
diff --git a/web-console/src/views/load-data-view/schema-table/schema-table.tsx b/web-console/src/views/load-data-view/schema-table/schema-table.tsx
index fbbaefa..bbfc5f9 100644
--- a/web-console/src/views/load-data-view/schema-table/schema-table.tsx
+++ b/web-console/src/views/load-data-view/schema-table/schema-table.tsx
@@ -45,15 +45,15 @@ export interface SchemaTableProps {
   selectedDimensionSpecIndex: number;
   selectedMetricSpecIndex: number;
   onDimensionOrMetricSelect: (
-    selectedDimensionSpec: DimensionSpec | null,
+    selectedDimensionSpec: DimensionSpec | undefined,
     selectedDimensionSpecIndex: number,
-    selectedMetricSpec: MetricSpec | null,
+    selectedMetricSpec: MetricSpec | undefined,
     selectedMetricSpecIndex: number,
   ) => void;
 }
 
 export class SchemaTable extends React.PureComponent<SchemaTableProps> {
-  render() {
+  render(): JSX.Element {
     const {
       sampleBundle,
       columnFilter,
@@ -88,7 +88,9 @@ export class SchemaTable extends React.PureComponent<SchemaTableProps> {
               Header: (
                 <div
                   className="clickable"
-                  onClick={() => onDimensionOrMetricSelect(null, -1, metricSpec, metricSpecIndex)}
+                  onClick={() =>
+                    onDimensionOrMetricSelect(undefined, -1, metricSpec, metricSpecIndex)
+                  }
                 >
                   <div className="column-name">{columnName}</div>
                   <div className="column-detail">{metricSpec.type}&nbsp;</div>
@@ -123,7 +125,7 @@ export class SchemaTable extends React.PureComponent<SchemaTableProps> {
                   className="clickable"
                   onClick={() => {
                     if (timestamp) {
-                      onDimensionOrMetricSelect(null, -1, null, -1);
+                      onDimensionOrMetricSelect(undefined, -1, undefined, -1);
                       return;
                     }
 
@@ -131,7 +133,7 @@ export class SchemaTable extends React.PureComponent<SchemaTableProps> {
                     onDimensionOrMetricSelect(
                       inflateDimensionSpec(dimensionSpec),
                       dimensionSpecIndex,
-                      null,
+                      undefined,
                       -1,
                     );
                   }}
diff --git a/web-console/src/views/load-data-view/transform-table/transform-table.spec.tsx b/web-console/src/views/load-data-view/transform-table/transform-table.spec.tsx
index 99608e8..964c338 100644
--- a/web-console/src/views/load-data-view/transform-table/transform-table.spec.tsx
+++ b/web-console/src/views/load-data-view/transform-table/transform-table.spec.tsx
@@ -40,7 +40,7 @@ describe('transform table', () => {
         transformedColumnsOnly={false}
         transforms={[]}
         selectedTransformIndex={-1}
-        onTransformSelect={() => null}
+        onTransformSelect={() => {}}
       />
     );
 
diff --git a/web-console/src/views/load-data-view/transform-table/transform-table.tsx b/web-console/src/views/load-data-view/transform-table/transform-table.tsx
index 50c3e01..fb5bd79 100644
--- a/web-console/src/views/load-data-view/transform-table/transform-table.tsx
+++ b/web-console/src/views/load-data-view/transform-table/transform-table.tsx
@@ -38,7 +38,7 @@ export interface TransformTableProps {
 }
 
 export class TransformTable extends React.PureComponent<TransformTableProps> {
-  render() {
+  render(): JSX.Element {
     const {
       sampleData,
       columnFilter,
diff --git a/web-console/src/views/lookups-view/__snapshots__/lookups-view.spec.tsx.snap b/web-console/src/views/lookups-view/__snapshots__/lookups-view.spec.tsx.snap
index a57aea4..58c894d 100755
--- a/web-console/src/views/lookups-view/__snapshots__/lookups-view.spec.tsx.snap
+++ b/web-console/src/views/lookups-view/__snapshots__/lookups-view.spec.tsx.snap
@@ -14,7 +14,7 @@ exports[`lookups view matches snapshot 1`] = `
     <Blueprint3.Button
       icon="plus"
       onClick={[Function]}
-      text="Add"
+      text="Add lookup"
     />
     <TableColumnSelector
       columns={
diff --git a/web-console/src/views/lookups-view/lookups-view.tsx b/web-console/src/views/lookups-view/lookups-view.tsx
index 2b6bb19..c29cfd7 100644
--- a/web-console/src/views/lookups-view/lookups-view.tsx
+++ b/web-console/src/views/lookups-view/lookups-view.tsx
@@ -38,9 +38,9 @@ const DEFAULT_LOOKUP_TIER: string = '__default';
 export interface LookupsViewProps {}
 
 export interface LookupsViewState {
-  lookups: {}[] | null;
+  lookups?: any[];
   loadingLookups: boolean;
-  lookupsError: string | null;
+  lookupsError?: string;
   lookupsUninitialized: boolean;
   lookupEditDialogOpen: boolean;
   lookupEditName: string;
@@ -50,8 +50,8 @@ export interface LookupsViewState {
   isEdit: boolean;
   allLookupTiers: string[];
 
-  deleteLookupName: string | null;
-  deleteLookupTier: string | null;
+  deleteLookupName?: string;
+  deleteLookupTier?: string;
 
   hiddenColumns: LocalStorageBackedArray<string>;
 }
@@ -64,7 +64,6 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
     this.state = {
       lookups: [],
       loadingLookups: true,
-      lookupsError: null,
       lookupsUninitialized: false,
       lookupEditDialogOpen: false,
       lookupEditTier: '',
@@ -74,9 +73,6 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
       isEdit: false,
       allLookupTiers: [],
 
-      deleteLookupTier: null,
-      deleteLookupName: null,
-
       hiddenColumns: new LocalStorageBackedArray<string>(
         LocalStorageKeys.LOOKUP_TABLE_COLUMN_SELECTION,
       ),
@@ -110,11 +106,11 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
       },
       onStateChange: ({ result, loading, error }) => {
         this.setState({
-          lookups: result ? result.lookupEntries : null,
+          lookups: result ? result.lookupEntries : undefined,
           loadingLookups: loading,
           lookupsError: error,
           lookupsUninitialized: error === 'Request failed with status code 404',
-          allLookupTiers: result === null ? [] : result.tiers,
+          allLookupTiers: result ? result.tiers : [],
         });
       },
     });
@@ -169,11 +165,11 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
     }
   }
 
-  private changeLookup(field: string, value: string) {
+  private handleChangeLookup = (field: string, value: string) => {
     this.setState({
       [field]: value,
     } as any);
-  }
+  };
 
   private async submitLookupEdit() {
     const {
@@ -249,7 +245,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
         failText="Could not delete lookup"
         intent={Intent.DANGER}
         onClose={() => {
-          this.setState({ deleteLookupTier: null, deleteLookupName: null });
+          this.setState({ deleteLookupTier: undefined, deleteLookupName: undefined });
         }}
         onSuccess={() => {
           this.lookupsQueryManager.rerunLastQuery();
@@ -274,7 +270,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
         <div className="init-div">
           <Button
             icon={IconNames.BUILD}
-            text="Initialize lookup"
+            text="Initialize lookups"
             onClick={() => this.initializeLookup()}
           />
         </div>
@@ -356,7 +352,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
         isOpen={lookupEditDialogOpen}
         onClose={() => this.setState({ lookupEditDialogOpen: false })}
         onSubmit={() => this.submitLookupEdit()}
-        onChange={(field: string, value: string) => this.changeLookup(field, value)}
+        onChange={this.handleChangeLookup}
         lookupSpec={lookupEditSpec}
         lookupName={lookupEditName}
         lookupTier={lookupEditTier}
@@ -367,7 +363,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
     );
   }
 
-  render() {
+  render(): JSX.Element {
     const { lookupsError, hiddenColumns } = this.state;
 
     return (
@@ -380,7 +376,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
           {!lookupsError && (
             <Button
               icon={IconNames.PLUS}
-              text="Add"
+              text="Add lookup"
               onClick={() => this.openLookupEditDialog('', '')}
             />
           )}
diff --git a/web-console/src/views/query-view/__snapshots__/query-view.spec.tsx.snap b/web-console/src/views/query-view/__snapshots__/query-view.spec.tsx.snap
index 3e4cfff..bbcdaf8 100644
--- a/web-console/src/views/query-view/__snapshots__/query-view.spec.tsx.snap
+++ b/web-console/src/views/query-view/__snapshots__/query-view.spec.tsx.snap
@@ -5,7 +5,6 @@ exports[`sql view matches snapshot 1`] = `
   className="query-view app-view"
 >
   <ColumnTree
-    columnMetadata={null}
     columnMetadataLoading={true}
     onQueryStringChange={[Function]}
   />
@@ -25,7 +24,6 @@ exports[`sql view matches snapshot 1`] = `
       className="control-pane"
     >
       <QueryInput
-        columnMetadata={null}
         onQueryStringChange={[Function]}
         queryString="test"
         runeMode={false}
@@ -43,9 +41,7 @@ exports[`sql view matches snapshot 1`] = `
       </div>
     </div>
     <QueryOutput
-      error={null}
       loading={false}
-      result={null}
     />
   </t>
 </div>
diff --git a/web-console/src/views/query-view/column-tree/column-tree.spec.tsx b/web-console/src/views/query-view/column-tree/column-tree.spec.tsx
index bdfe4fc..e8ca98d 100644
--- a/web-console/src/views/query-view/column-tree/column-tree.spec.tsx
+++ b/web-console/src/views/query-view/column-tree/column-tree.spec.tsx
@@ -50,7 +50,7 @@ describe('column tree', () => {
             },
           ] as ColumnMetadata[]
         }
-        onQueryStringChange={() => null}
+        onQueryStringChange={() => {}}
       />
     );
 
diff --git a/web-console/src/views/query-view/column-tree/column-tree.tsx b/web-console/src/views/query-view/column-tree/column-tree.tsx
index f95708a..4e0d24f 100644
--- a/web-console/src/views/query-view/column-tree/column-tree.tsx
+++ b/web-console/src/views/query-view/column-tree/column-tree.tsx
@@ -28,13 +28,13 @@ import './column-tree.scss';
 
 export interface ColumnTreeProps {
   columnMetadataLoading: boolean;
-  columnMetadata: ColumnMetadata[] | null;
+  columnMetadata?: ColumnMetadata[];
   onQueryStringChange: (queryString: string) => void;
 }
 
 export interface ColumnTreeState {
-  prevColumnMetadata: ColumnMetadata[] | null;
-  columnTree: ITreeNode[] | null;
+  prevColumnMetadata?: ColumnMetadata[];
+  columnTree?: ITreeNode[];
   selectedTreeIndex: number;
 }
 
@@ -88,8 +88,6 @@ export class ColumnTree extends React.PureComponent<ColumnTreeProps, ColumnTreeS
   constructor(props: ColumnTreeProps, context: any) {
     super(props, context);
     this.state = {
-      prevColumnMetadata: null,
-      columnTree: null,
       selectedTreeIndex: 0,
     };
   }
@@ -120,7 +118,7 @@ export class ColumnTree extends React.PureComponent<ColumnTreeProps, ColumnTreeS
     this.setState({ selectedTreeIndex: Number(e.target.value) });
   };
 
-  render() {
+  render(): JSX.Element | null {
     const { columnMetadataLoading } = this.props;
     if (columnMetadataLoading) {
       return (
@@ -155,21 +153,28 @@ export class ColumnTree extends React.PureComponent<ColumnTreeProps, ColumnTreeS
     const { columnTree, selectedTreeIndex } = this.state;
     if (!columnTree) return;
 
+    const selectedNode = columnTree[selectedTreeIndex];
     switch (nodePath.length) {
       case 1: // Datasource
-        const tableSchema = columnTree[selectedTreeIndex].label;
+        const tableSchema = selectedNode.label;
+        let columns: string[];
+        if (nodeData.childNodes) {
+          columns = nodeData.childNodes.map(child => String(child.label));
+        } else {
+          columns = ['*'];
+        }
         if (tableSchema === 'druid') {
-          onQueryStringChange(`SELECT *
+          onQueryStringChange(`SELECT ${columns.join(', ')}
 FROM "${nodeData.label}"
 WHERE "__time" >= CURRENT_TIMESTAMP - INTERVAL '1' DAY`);
         } else {
-          onQueryStringChange(`SELECT *
+          onQueryStringChange(`SELECT ${columns.join(', ')}
 FROM ${tableSchema}.${nodeData.label}`);
         }
         break;
 
       case 2: // Column
-        const schemaNode = columnTree[selectedTreeIndex];
+        const schemaNode = selectedNode;
         const columnSchema = schemaNode.label;
         const columnTable = schemaNode.childNodes ? schemaNode.childNodes[nodePath[0]].label : '?';
         if (columnSchema === 'druid') {
diff --git a/web-console/src/views/query-view/query-extra-info/query-extra-info.spec.tsx b/web-console/src/views/query-view/query-extra-info/query-extra-info.spec.tsx
index d173ceb..fb1ea2e 100644
--- a/web-console/src/views/query-view/query-extra-info/query-extra-info.spec.tsx
+++ b/web-console/src/views/query-view/query-extra-info/query-extra-info.spec.tsx
@@ -27,13 +27,12 @@ describe('query extra info', () => {
       <QueryExtraInfo
         queryExtraInfo={{
           queryId: 'e3ee781b-c0b6-4385-9d99-a8a1994bebac',
-          sqlQueryId: null,
           startTime: new Date('1986-04-26T01:23:40+03:00'),
           endTime: new Date('1986-04-26T01:23:48+03:00'),
           numResults: 2000,
           wrappedLimit: 2000,
         }}
-        onDownload={() => null}
+        onDownload={() => {}}
       />
     );
 
diff --git a/web-console/src/views/query-view/query-extra-info/query-extra-info.tsx b/web-console/src/views/query-view/query-extra-info/query-extra-info.tsx
index d4dbbd6..a25cd79 100644
--- a/web-console/src/views/query-view/query-extra-info/query-extra-info.tsx
+++ b/web-console/src/views/query-view/query-extra-info/query-extra-info.tsx
@@ -36,8 +36,8 @@ import { pluralIfNeeded } from '../../../utils';
 import './query-extra-info.scss';
 
 export interface QueryExtraInfoData {
-  queryId: string | null;
-  sqlQueryId: string | null;
+  queryId?: string;
+  sqlQueryId?: string;
   startTime: Date;
   endTime: Date;
   numResults: number;
@@ -50,7 +50,7 @@ export interface QueryExtraInfoProps {
 }
 
 export class QueryExtraInfo extends React.PureComponent<QueryExtraInfoProps> {
-  render() {
+  render(): JSX.Element {
     const { queryExtraInfo } = this.props;
 
     const downloadMenu = (
diff --git a/web-console/src/views/query-view/query-input/query-input.spec.tsx b/web-console/src/views/query-view/query-input/query-input.spec.tsx
index b78ba60..f391e3d 100644
--- a/web-console/src/views/query-view/query-input/query-input.spec.tsx
+++ b/web-console/src/views/query-view/query-input/query-input.spec.tsx
@@ -24,12 +24,7 @@ import { QueryInput } from './query-input';
 describe('query input', () => {
   it('matches snapshot', () => {
     const sqlControl = (
-      <QueryInput
-        queryString="hello world"
-        onQueryStringChange={() => null}
-        runeMode={false}
-        columnMetadata={null}
-      />
+      <QueryInput queryString="hello world" onQueryStringChange={() => {}} runeMode={false} />
     );
 
     const { container } = render(sqlControl);
diff --git a/web-console/src/views/query-view/query-input/query-input.tsx b/web-console/src/views/query-view/query-input/query-input.tsx
index 3d65057..0970888 100644
--- a/web-console/src/views/query-view/query-input/query-input.tsx
+++ b/web-console/src/views/query-view/query-input/query-input.tsx
@@ -18,9 +18,9 @@
 
 import { IResizeEntry, ResizeSensor } from '@blueprintjs/core';
 import ace from 'brace';
+import escape from 'lodash.escape';
 import React from 'react';
 import AceEditor from 'react-ace';
-import ReactDOMServer from 'react-dom/server';
 
 import { SQL_DATE_TYPES, SQL_FUNCTIONS, SyntaxDescription } from '../../../../lib/sql-function-doc';
 import { uniq } from '../../../utils';
@@ -37,14 +37,14 @@ export interface QueryInputProps {
   queryString: string;
   onQueryStringChange: (newQueryString: string) => void;
   runeMode: boolean;
-  columnMetadata: ColumnMetadata[] | null;
+  columnMetadata?: ColumnMetadata[];
 }
 
 export interface QueryInputState {
   // For reasons (https://github.com/securingsincity/react-ace/issues/415) react ace editor needs an explicit height
   // Since this component will grown and shrink dynamically we will measure its height and then set it.
   editorHeight: number;
-  prevColumnMetadata: ColumnMetadata[] | null;
+  prevColumnMetadata?: ColumnMetadata[];
 }
 
 export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputState> {
@@ -87,7 +87,6 @@ export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputS
     super(props, context);
     this.state = {
       editorHeight: 200,
-      prevColumnMetadata: null,
     };
   }
 
@@ -143,23 +142,22 @@ export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputS
       getDocTooltip: (item: any) => {
         if (item.meta === 'function') {
           const functionName = item.caption.slice(0, -2);
-          item.docHTML = ReactDOMServer.renderToStaticMarkup(
-            <div className="function-doc">
-              <div className="function-doc-name">
-                <b>{functionName}</b>
-              </div>
-              <hr />
-              <div>
-                <b>Syntax:</b>
-              </div>
-              <div>{item.syntax}</div>
-              <br />
-              <div>
-                <b>Description:</b>
-              </div>
-              <div>{item.description}</div>
-            </div>,
-          );
+          item.docHTML = `
+<div class="function-doc">
+  <div class="function-doc-name">
+    <b>${escape(functionName)}</b>
+  </div>
+  <hr />
+  <div>
+    <b>Syntax:</b>
+  </div>
+  <div>${escape(item.syntax)}</div>
+  <br />
+  <div>
+    <b>Description:</b>
+  </div>
+  <div>${escape(item.description)}</div>
+</div>`;
         }
       },
     });
@@ -175,7 +173,7 @@ export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputS
     this.setState({ editorHeight: entries[0].contentRect.height });
   };
 
-  render() {
+  render(): JSX.Element {
     const { queryString, runeMode, onQueryStringChange } = this.props;
     const { editorHeight } = this.state;
 
@@ -205,6 +203,7 @@ export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputS
                 tabSize: 2,
               }}
               style={{}}
+              placeholder="SELECT * FROM ..."
             />
           </div>
         </ResizeSensor>
diff --git a/web-console/src/views/query-view/query-output/query-output.spec.tsx b/web-console/src/views/query-view/query-output/query-output.spec.tsx
index 3852b72..8c18e07 100644
--- a/web-console/src/views/query-view/query-output/query-output.spec.tsx
+++ b/web-console/src/views/query-view/query-output/query-output.spec.tsx
@@ -23,7 +23,7 @@ import { QueryOutput } from './query-output';
 
 describe('query output', () => {
   it('matches snapshot', () => {
-    const queryOutput = <QueryOutput loading={false} result={null} error="lol" />;
+    const queryOutput = <QueryOutput loading={false} error="lol" />;
 
     const { container } = render(queryOutput);
     expect(container.firstChild).toMatchSnapshot();
diff --git a/web-console/src/views/query-view/query-output/query-output.tsx b/web-console/src/views/query-view/query-output/query-output.tsx
index e35856a..ec3428f 100644
--- a/web-console/src/views/query-view/query-output/query-output.tsx
+++ b/web-console/src/views/query-view/query-output/query-output.tsx
@@ -26,12 +26,12 @@ import './query-output.scss';
 
 export interface QueryOutputProps {
   loading: boolean;
-  result: HeaderRows | null;
-  error: string | null;
+  result?: HeaderRows;
+  error?: string;
 }
 
 export class QueryOutput extends React.PureComponent<QueryOutputProps> {
-  render() {
+  render(): JSX.Element {
     const { result, loading, error } = this.props;
 
     return (
diff --git a/web-console/src/views/query-view/query-view.tsx b/web-console/src/views/query-view/query-view.tsx
index 6395409..8408ddf 100644
--- a/web-console/src/views/query-view/query-view.tsx
+++ b/web-console/src/views/query-view/query-view.tsx
@@ -65,18 +65,18 @@ export interface QueryViewState {
   queryContext: QueryContext;
 
   columnMetadataLoading: boolean;
-  columnMetadata: ColumnMetadata[] | null;
-  columnMetadataError: string | null;
+  columnMetadata?: ColumnMetadata[];
+  columnMetadataError?: string;
 
   loading: boolean;
-  result: HeaderRows | null;
-  queryExtraInfo: QueryExtraInfoData | null;
-  error: string | null;
+  result?: HeaderRows;
+  queryExtraInfo?: QueryExtraInfoData;
+  error?: string;
 
   explainDialogOpen: boolean;
-  explainResult: BasicQueryExplanation | SemiJoinQueryExplanation | string | null;
+  explainResult?: BasicQueryExplanation | SemiJoinQueryExplanation | string;
   loadingExplain: boolean;
-  explainError: Error | null;
+  explainError?: string;
 }
 
 interface QueryResult {
@@ -133,18 +133,11 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
       queryContext: {},
 
       columnMetadataLoading: false,
-      columnMetadata: null,
-      columnMetadataError: null,
 
       loading: false,
-      result: null,
-      queryExtraInfo: null,
-      error: null,
 
       explainDialogOpen: false,
       loadingExplain: false,
-      explainResult: null,
-      explainError: null,
     };
 
     this.metadataQueryManager = new QueryManager({
@@ -171,8 +164,8 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
     this.sqlQueryManager = new QueryManager({
       processQuery: async (queryWithContext: QueryWithContext): Promise<QueryResult> => {
         const { queryString, queryContext, wrapQuery } = queryWithContext;
-        let queryId: string | null = null;
-        let sqlQueryId: string | null = null;
+        let queryId: string | undefined;
+        let sqlQueryId: string | undefined;
         let wrappedLimit: number | undefined;
 
         let queryResult: HeaderRows;
@@ -239,8 +232,8 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
       },
       onStateChange: ({ result, loading, error }) => {
         this.setState({
-          result: result ? result.queryResult : null,
-          queryExtraInfo: result ? result.queryExtraInfo : null,
+          result: result ? result.queryResult : undefined,
+          queryExtraInfo: result ? result.queryExtraInfo : undefined,
           loading,
           error,
         });
@@ -264,7 +257,7 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
         this.setState({
           explainResult: result,
           loadingExplain: loading,
-          explainError: error !== null ? new Error(error) : null,
+          explainError: error,
         });
       },
     });
@@ -312,16 +305,15 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
 
   renderExplainDialog() {
     const { explainDialogOpen, explainResult, loadingExplain, explainError } = this.state;
-    if (!loadingExplain && explainDialogOpen) {
-      return (
-        <QueryPlanDialog
-          explainResult={explainResult}
-          explainError={explainError}
-          onClose={() => this.setState({ explainDialogOpen: false })}
-        />
-      );
-    }
-    return null;
+    if (loadingExplain || !explainDialogOpen) return;
+
+    return (
+      <QueryPlanDialog
+        explainResult={explainResult}
+        explainError={explainError}
+        onClose={() => this.setState({ explainDialogOpen: false })}
+      />
+    );
   }
 
   renderMainArea() {
@@ -399,7 +391,7 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
     localStorageSet(LocalStorageKeys.QUERY_VIEW_PANE_SIZE, String(secondaryPaneSize));
   };
 
-  render() {
+  render(): JSX.Element {
     const { columnMetadata, columnMetadataLoading, columnMetadataError } = this.state;
 
     return (
diff --git a/web-console/src/views/query-view/run-button/run-button.spec.tsx b/web-console/src/views/query-view/run-button/run-button.spec.tsx
index 4529cc7..cbc57ae 100644
--- a/web-console/src/views/query-view/run-button/run-button.spec.tsx
+++ b/web-console/src/views/query-view/run-button/run-button.spec.tsx
@@ -27,9 +27,9 @@ describe('run button', () => {
       <RunButton
         runeMode={false}
         queryContext={{}}
-        onQueryContextChange={() => null}
-        onRun={() => null}
-        onExplain={() => null}
+        onQueryContextChange={() => {}}
+        onRun={() => {}}
+        onExplain={() => {}}
       />
     );
 
diff --git a/web-console/src/views/query-view/run-button/run-button.tsx b/web-console/src/views/query-view/run-button/run-button.tsx
index f987c13..78fecfc 100644
--- a/web-console/src/views/query-view/run-button/run-button.tsx
+++ b/web-console/src/views/query-view/run-button/run-button.tsx
@@ -47,7 +47,7 @@ export interface RunButtonProps {
   runeMode: boolean;
   queryContext: QueryContext;
   onQueryContextChange: (newQueryContext: QueryContext) => void;
-  onRun: ((wrapQuery: boolean) => void) | null;
+  onRun: (wrapQuery: boolean) => void;
   onExplain: () => void;
 }
 
@@ -137,7 +137,7 @@ export class RunButton extends React.PureComponent<RunButtonProps, RunButtonStat
     );
   }
 
-  render() {
+  render(): JSX.Element {
     const { runeMode, onRun } = this.props;
     const { wrapQuery } = this.state;
 
diff --git a/web-console/src/views/segments-view/segments-view.tsx b/web-console/src/views/segments-view/segments-view.tsx
index 33b5c2b..8ec0cc8 100644
--- a/web-console/src/views/segments-view/segments-view.tsx
+++ b/web-console/src/views/segments-view/segments-view.tsx
@@ -28,6 +28,7 @@ import { AsyncActionDialog } from '../../dialogs';
 import { SegmentTableActionDialog } from '../../dialogs/segments-table-action-dialog/segment-table-action-dialog';
 import {
   addFilter,
+  filterMap,
   formatBytes,
   formatNumber,
   LocalStorageKeys,
@@ -76,15 +77,15 @@ export interface SegmentsViewProps {
 
 export interface SegmentsViewState {
   segmentsLoading: boolean;
-  segments: SegmentQueryResultRow[] | null;
-  segmentsError: string | null;
+  segments?: SegmentQueryResultRow[];
+  segmentsError?: string;
   segmentFilter: Filter[];
-  allSegments?: SegmentQueryResultRow[] | null;
-  segmentTableActionDialogId: string | null;
-  datasourceTableActionDialogId: string | null;
+  allSegments?: SegmentQueryResultRow[];
+  segmentTableActionDialogId?: string;
+  datasourceTableActionDialogId?: string;
   actions: BasicAction[];
-  terminateSegmentId: string | null;
-  terminateDatasourceId: string | null;
+  terminateSegmentId?: string;
+  terminateDatasourceId?: string;
   hiddenColumns: LocalStorageBackedArray<string>;
   loaded: boolean;
   groupByInterval: boolean;
@@ -140,14 +141,8 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
     if (props.onlyUnavailable) segmentFilter.push({ id: 'is_available', value: 'false' });
 
     this.state = {
-      segmentTableActionDialogId: null,
-      datasourceTableActionDialogId: null,
       actions: [],
-      terminateSegmentId: null,
-      terminateDatasourceId: null,
       segmentsLoading: true,
-      segments: null,
-      segmentsError: null,
       segmentFilter,
       hiddenColumns: new LocalStorageBackedArray<string>(
         LocalStorageKeys.SEGMENT_TABLE_COLUMN_SELECTION,
@@ -166,16 +161,14 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
       processQuery: async (query: SegmentsQuery, setIntermediateQuery) => {
         const totalQuerySize = (query.page + 1) * query.pageSize;
 
-        const whereParts = query.filtered
-          .map((f: Filter) => {
-            if (f.id.startsWith('is_')) {
-              if (f.value === 'all') return null;
-              return `${JSON.stringify(f.id)} = ${f.value === 'true' ? 1 : 0}`;
-            } else {
-              return sqlQueryCustomTableFilter(f);
-            }
-          })
-          .filter(Boolean);
+        const whereParts = filterMap(query.filtered, (f: Filter) => {
+          if (f.id.startsWith('is_')) {
+            if (f.value === 'all') return;
+            return `${JSON.stringify(f.id)} = ${f.value === 'true' ? 1 : 0}`;
+          } else {
+            return sqlQueryCustomTableFilter(f);
+          }
+        });
 
         let queryParts: string[];
         if (query.groupByInterval) {
@@ -258,6 +251,7 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
           datasourceList.map(async (d: string) => {
             const segments = (await axios.get(`/druid/coordinator/v1/datasources/${d}?full`)).data
               .segments;
+
             return segments.map((segment: any) => {
               return {
                 segment_id: segment.identifier,
@@ -278,17 +272,17 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
             });
           }),
         );
-        const results: SegmentQueryResultRow[] = ([] as SegmentQueryResultRow[]).concat
-          .apply([], nestedResults)
-          .sort((d1: any, d2: any) => {
-            return d2.start.localeCompare(d1.start);
-          });
-        return results;
+
+        const results: SegmentQueryResultRow[] = nestedResults.flat().sort((d1: any, d2: any) => {
+          return d2.start.localeCompare(d1.start);
+        });
+
+        return results.slice(0, SegmentsView.PAGE_SIZE);
       },
       onStateChange: ({ result, loading, error }) => {
         this.setState({
           allSegments: result,
-          segments: result ? result.slice(0, SegmentsView.PAGE_SIZE) : null,
+          segments: result,
           segmentsLoading: loading,
           segmentsError: error,
         });
@@ -599,7 +593,7 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
         failText="Could not drop segment"
         intent={Intent.DANGER}
         onClose={() => {
-          this.setState({ terminateSegmentId: null });
+          this.setState({ terminateSegmentId: undefined });
         }}
         onSuccess={() => {
           this.segmentsNoSqlQueryManager.rerunLastQuery();
@@ -612,7 +606,7 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
     );
   }
 
-  render() {
+  render(): JSX.Element {
     const {
       segmentTableActionDialogId,
       datasourceTableActionDialogId,
@@ -681,7 +675,7 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
             segmentId={segmentTableActionDialogId}
             dataSourceId={datasourceTableActionDialogId}
             actions={actions}
-            onClose={() => this.setState({ segmentTableActionDialogId: null })}
+            onClose={() => this.setState({ segmentTableActionDialogId: undefined })}
             isOpen
           />
         )}
diff --git a/web-console/src/views/servers-view/servers-view.tsx b/web-console/src/views/servers-view/servers-view.tsx
index 142faa0..516237c 100644
--- a/web-console/src/views/servers-view/servers-view.tsx
+++ b/web-console/src/views/servers-view/servers-view.tsx
@@ -83,13 +83,13 @@ export interface ServersViewProps {
 
 export interface ServersViewState {
   serversLoading: boolean;
-  servers: any[] | null;
-  serversError: string | null;
+  servers?: any[];
+  serversError?: string;
   serverFilter: Filter[];
-  groupServersBy: null | 'server_type' | 'tier';
+  groupServersBy?: 'server_type' | 'tier';
 
-  middleManagerDisableWorkerHost: string | null;
-  middleManagerEnableWorkerHost: string | null;
+  middleManagerDisableWorkerHost?: string;
+  middleManagerEnableWorkerHost?: string;
 
   hiddenColumns: LocalStorageBackedArray<string>;
 }
@@ -182,13 +182,7 @@ ORDER BY "rank" DESC, "server" DESC`;
     super(props, context);
     this.state = {
       serversLoading: true,
-      servers: null,
-      serversError: null,
       serverFilter: [],
-      groupServersBy: null,
-
-      middleManagerDisableWorkerHost: null,
-      middleManagerEnableWorkerHost: null,
 
       hiddenColumns: new LocalStorageBackedArray<string>(
         LocalStorageKeys.SERVER_TABLE_COLUMN_SELECTION,
@@ -576,7 +570,7 @@ ORDER BY "rank" DESC, "server" DESC`;
         failText="Could not disable worker"
         intent={Intent.DANGER}
         onClose={() => {
-          this.setState({ middleManagerDisableWorkerHost: null });
+          this.setState({ middleManagerDisableWorkerHost: undefined });
         }}
         onSuccess={() => {
           this.serverQueryManager.rerunLastQuery();
@@ -605,7 +599,7 @@ ORDER BY "rank" DESC, "server" DESC`;
         failText="Could not enable worker"
         intent={Intent.PRIMARY}
         onClose={() => {
-          this.setState({ middleManagerEnableWorkerHost: null });
+          this.setState({ middleManagerEnableWorkerHost: undefined });
         }}
         onSuccess={() => {
           this.serverQueryManager.rerunLastQuery();
@@ -616,7 +610,7 @@ ORDER BY "rank" DESC, "server" DESC`;
     );
   }
 
-  render() {
+  render(): JSX.Element {
     const { goToQuery, noSqlMode } = this.props;
     const { groupServersBy, hiddenColumns } = this.state;
 
@@ -626,8 +620,8 @@ ORDER BY "rank" DESC, "server" DESC`;
           <Label>Group by</Label>
           <ButtonGroup>
             <Button
-              active={groupServersBy === null}
-              onClick={() => this.setState({ groupServersBy: null })}
+              active={!groupServersBy}
+              onClick={() => this.setState({ groupServersBy: undefined })}
             >
               None
             </Button>
diff --git a/web-console/src/views/task-view/tasks-view.spec.tsx b/web-console/src/views/task-view/tasks-view.spec.tsx
index 254f2ce..93bc323 100644
--- a/web-console/src/views/task-view/tasks-view.spec.tsx
+++ b/web-console/src/views/task-view/tasks-view.spec.tsx
@@ -27,9 +27,9 @@ describe('tasks view', () => {
       <TasksView
         openDialog={'test'}
         taskId={'test'}
-        goToQuery={() => null}
-        goToMiddleManager={() => null}
-        goToLoadDataView={() => null}
+        goToQuery={() => {}}
+        goToMiddleManager={() => {}}
+        goToLoadDataView={() => {}}
         noSqlMode={false}
       />,
     );
diff --git a/web-console/src/views/task-view/tasks-view.tsx b/web-console/src/views/task-view/tasks-view.tsx
index 28c0541..9480d3c 100644
--- a/web-console/src/views/task-view/tasks-view.tsx
+++ b/web-console/src/views/task-view/tasks-view.tsx
@@ -88,34 +88,33 @@ export interface TasksViewProps {
 export interface TasksViewState {
   supervisorsLoading: boolean;
   supervisors: any[];
-  supervisorsError: string | null;
+  supervisorsError?: string;
 
-  resumeSupervisorId: string | null;
-  suspendSupervisorId: string | null;
-  resetSupervisorId: string | null;
-  terminateSupervisorId: string | null;
+  resumeSupervisorId?: string;
+  suspendSupervisorId?: string;
+  resetSupervisorId?: string;
+  terminateSupervisorId?: string;
 
   showResumeAllSupervisors: boolean;
   showSuspendAllSupervisors: boolean;
   showTerminateAllSupervisors: boolean;
 
   tasksLoading: boolean;
-  tasks: any[] | null;
-  tasksError: string | null;
+  tasks?: any[];
+  tasksError?: string;
   taskFilter: Filter[];
-  groupTasksBy: null | 'type' | 'datasource' | 'status';
+  groupTasksBy?: 'type' | 'datasource' | 'status';
 
-  killTaskId: string | null;
+  killTaskId?: string;
 
   supervisorSpecDialogOpen: boolean;
   taskSpecDialogOpen: boolean;
-  initSpec: any;
-  alertErrorMsg: string | null;
+  alertErrorMsg?: string;
 
-  taskTableActionDialogId: string | null;
-  taskTableActionDialogStatus: string | null;
+  taskTableActionDialogId?: string;
+  taskTableActionDialogStatus?: string;
   taskTableActionDialogActions: BasicAction[];
-  supervisorTableActionDialogId: string | null;
+  supervisorTableActionDialogId?: string;
   supervisorTableActionDialogActions: BasicAction[];
   hiddenTaskColumns: LocalStorageBackedArray<string>;
   hiddenSupervisorColumns: LocalStorageBackedArray<string>;
@@ -202,33 +201,17 @@ ORDER BY "rank" DESC, "created_time" DESC`;
     this.state = {
       supervisorsLoading: true,
       supervisors: [],
-      supervisorsError: null,
-
-      resumeSupervisorId: null,
-      suspendSupervisorId: null,
-      resetSupervisorId: null,
-      supervisorTableActionDialogId: null,
-      terminateSupervisorId: null,
 
       showResumeAllSupervisors: false,
       showSuspendAllSupervisors: false,
       showTerminateAllSupervisors: false,
 
       tasksLoading: true,
-      tasks: null,
-      tasksError: null,
       taskFilter: props.taskId ? [{ id: 'task_id', value: props.taskId }] : [],
-      groupTasksBy: null,
-
-      killTaskId: null,
 
       supervisorSpecDialogOpen: props.openDialog === 'supervisor',
       taskSpecDialogOpen: props.openDialog === 'task',
-      initSpec: null,
-      alertErrorMsg: null,
 
-      taskTableActionDialogId: null,
-      taskTableActionDialogStatus: null,
       taskTableActionDialogActions: [],
       supervisorTableActionDialogActions: [],
 
@@ -323,7 +306,6 @@ ORDER BY "rank" DESC, "created_time" DESC`;
     this.setState({
       supervisorSpecDialogOpen: false,
       taskSpecDialogOpen: false,
-      initSpec: null,
     });
   };
 
@@ -378,11 +360,6 @@ ORDER BY "rank" DESC, "created_time" DESC`;
     }
     actions.push(
       {
-        icon: IconNames.STEP_BACKWARD,
-        title: 'Reset',
-        onAction: () => this.setState({ resetSupervisorId: id }),
-      },
-      {
         icon: supervisorSuspended ? IconNames.PLAY : IconNames.PAUSE,
         title: supervisorSuspended ? 'Resume' : 'Suspend',
         onAction: () =>
@@ -391,6 +368,12 @@ ORDER BY "rank" DESC, "created_time" DESC`;
             : this.setState({ suspendSupervisorId: id }),
       },
       {
+        icon: IconNames.STEP_BACKWARD,
+        title: 'Reset',
+        intent: Intent.DANGER,
+        onAction: () => this.setState({ resetSupervisorId: id }),
+      },
+      {
         icon: IconNames.CROSS,
         title: 'Terminate',
         intent: Intent.DANGER,
@@ -418,7 +401,7 @@ ORDER BY "rank" DESC, "created_time" DESC`;
         failText="Could not resume supervisor"
         intent={Intent.PRIMARY}
         onClose={() => {
-          this.setState({ resumeSupervisorId: null });
+          this.setState({ resumeSupervisorId: undefined });
         }}
         onSuccess={() => {
           this.supervisorQueryManager.rerunLastQuery();
@@ -447,7 +430,7 @@ ORDER BY "rank" DESC, "created_time" DESC`;
         failText="Could not suspend supervisor"
         intent={Intent.DANGER}
         onClose={() => {
-          this.setState({ suspendSupervisorId: null });
+          this.setState({ suspendSupervisorId: undefined });
         }}
         onSuccess={() => {
           this.supervisorQueryManager.rerunLastQuery();
@@ -476,13 +459,14 @@ ORDER BY "rank" DESC, "created_time" DESC`;
         failText="Could not reset supervisor"
         intent={Intent.DANGER}
         onClose={() => {
-          this.setState({ resetSupervisorId: null });
+          this.setState({ resetSupervisorId: undefined });
         }}
         onSuccess={() => {
           this.supervisorQueryManager.rerunLastQuery();
         }}
       >
         <p>{`Are you sure you want to reset supervisor '${resetSupervisorId}'?`}</p>
+        <p>Resetting a supervisor could lead data loss or data duplication</p>
       </AsyncActionDialog>
     );
   }
@@ -505,7 +489,7 @@ ORDER BY "rank" DESC, "created_time" DESC`;
         failText="Could not terminate supervisor"
         intent={Intent.DANGER}
         onClose={() => {
-          this.setState({ terminateSupervisorId: null });
+          this.setState({ terminateSupervisorId: undefined });
         }}
         onSuccess={() => {
           this.supervisorQueryManager.rerunLastQuery();
@@ -655,7 +639,7 @@ ORDER BY "rank" DESC, "created_time" DESC`;
         failText="Could not kill task"
         intent={Intent.DANGER}
         onClose={() => {
-          this.setState({ killTaskId: null });
+          this.setState({ killTaskId: undefined });
         }}
         onSuccess={() => {
           this.taskQueryManager.rerunLastQuery();
@@ -964,7 +948,7 @@ ORDER BY "rank" DESC, "created_time" DESC`;
     );
   }
 
-  render() {
+  render(): JSX.Element {
     const { goToQuery, goToLoadDataView, noSqlMode } = this.props;
     const {
       groupTasksBy,
@@ -1048,8 +1032,8 @@ ORDER BY "rank" DESC, "created_time" DESC`;
               <Label>Group by</Label>
               <ButtonGroup>
                 <Button
-                  active={groupTasksBy === null}
-                  onClick={() => this.setState({ groupTasksBy: null })}
+                  active={!groupTasksBy}
+                  onClick={() => this.setState({ groupTasksBy: undefined })}
                 >
                   None
                 </Button>
@@ -1116,7 +1100,7 @@ ORDER BY "rank" DESC, "created_time" DESC`;
           intent={Intent.PRIMARY}
           isOpen={Boolean(alertErrorMsg)}
           confirmButtonText="OK"
-          onConfirm={() => this.setState({ alertErrorMsg: null })}
+          onConfirm={() => this.setState({ alertErrorMsg: undefined })}
         >
           <p>{alertErrorMsg}</p>
         </Alert>
@@ -1125,16 +1109,16 @@ ORDER BY "rank" DESC, "created_time" DESC`;
             isOpen
             supervisorId={supervisorTableActionDialogId}
             actions={supervisorTableActionDialogActions}
-            onClose={() => this.setState({ supervisorTableActionDialogId: null })}
+            onClose={() => this.setState({ supervisorTableActionDialogId: undefined })}
           />
         )}
-        {taskTableActionDialogId && (
+        {taskTableActionDialogId && taskTableActionDialogStatus && (
           <TaskTableActionDialog
             isOpen
             status={taskTableActionDialogStatus}
             taskId={taskTableActionDialogId}
             actions={taskTableActionDialogActions}
-            onClose={() => this.setState({ taskTableActionDialogId: null })}
+            onClose={() => this.setState({ taskTableActionDialogId: undefined })}
           />
         )}
       </>
diff --git a/web-console/tsconfig.json b/web-console/tsconfig.json
index 0e43c57..4472d5a 100644
--- a/web-console/tsconfig.json
+++ b/web-console/tsconfig.json
@@ -16,7 +16,7 @@
     "target": "es5",
     "module": "commonjs",
     "moduleResolution": "node",
-    "lib": ["dom", "es2016"],
+    "lib": ["dom", "es2016", "esnext"],
     "jsx": "react",
     "rootDirs": ["lib", "src"]
   },


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org