Web console: fix lookup edit dialog version setting (#10461)

* fix lookup edit dialog

* update snapshots

* clean up test
diff --git a/web-console/src/components/auto-form/auto-form.tsx b/web-console/src/components/auto-form/auto-form.tsx
index 59561ac..ce26cad 100644
--- a/web-console/src/components/auto-form/auto-form.tsx
+++ b/web-console/src/components/auto-form/auto-form.tsx
@@ -50,6 +50,7 @@
   placeholder?: Functor<M, string>;
   min?: number;
   zeroMeansUndefined?: boolean;
+  height?: string;
   disabled?: Functor<M, boolean>;
   defined?: Functor<M, boolean>;
   required?: Functor<M, boolean>;
@@ -272,6 +273,7 @@
         value={deepGet(model as any, field.name)}
         onChange={(v: any) => this.fieldChange(field, v)}
         placeholder={AutoForm.evaluateFunctor(field.placeholder, model, '')}
+        height={field.height}
       />
     );
   }
diff --git a/web-console/src/components/form-json-selector/__snapshots__/form-json-selector.spec.tsx.snap b/web-console/src/components/form-json-selector/__snapshots__/form-json-selector.spec.tsx.snap
new file mode 100644
index 0000000..d2ec216
--- /dev/null
+++ b/web-console/src/components/form-json-selector/__snapshots__/form-json-selector.spec.tsx.snap
@@ -0,0 +1,43 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`FormJsonSelector matches snapshot form json 1`] = `
+<Blueprint3.FormGroup
+  className="form-json-selector"
+>
+  <Blueprint3.ButtonGroup
+    fill={true}
+  >
+    <Blueprint3.Button
+      active={false}
+      onClick={[Function]}
+      text="Form"
+    />
+    <Blueprint3.Button
+      active={true}
+      onClick={[Function]}
+      text="JSON"
+    />
+  </Blueprint3.ButtonGroup>
+</Blueprint3.FormGroup>
+`;
+
+exports[`FormJsonSelector matches snapshot form tab 1`] = `
+<Blueprint3.FormGroup
+  className="form-json-selector"
+>
+  <Blueprint3.ButtonGroup
+    fill={true}
+  >
+    <Blueprint3.Button
+      active={true}
+      onClick={[Function]}
+      text="Form"
+    />
+    <Blueprint3.Button
+      active={false}
+      onClick={[Function]}
+      text="JSON"
+    />
+  </Blueprint3.ButtonGroup>
+</Blueprint3.FormGroup>
+`;
diff --git a/web-console/src/components/form-json-selector/form-json-selector.spec.tsx b/web-console/src/components/form-json-selector/form-json-selector.spec.tsx
new file mode 100644
index 0000000..ae7c3a9
--- /dev/null
+++ b/web-console/src/components/form-json-selector/form-json-selector.spec.tsx
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { shallow } from 'enzyme';
+import React from 'react';
+
+import { FormJsonSelector } from './form-json-selector';
+
+describe('FormJsonSelector', () => {
+  it('matches snapshot form tab', () => {
+    const formJsonSelector = shallow(<FormJsonSelector tab="form" onChange={() => {}} />);
+
+    expect(formJsonSelector).toMatchSnapshot();
+  });
+
+  it('matches snapshot form json', () => {
+    const formJsonSelector = shallow(<FormJsonSelector tab="json" onChange={() => {}} />);
+
+    expect(formJsonSelector).toMatchSnapshot();
+  });
+});
diff --git a/web-console/src/components/form-json-selector/form-json-selector.tsx b/web-console/src/components/form-json-selector/form-json-selector.tsx
new file mode 100644
index 0000000..4999826
--- /dev/null
+++ b/web-console/src/components/form-json-selector/form-json-selector.tsx
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Button, ButtonGroup, FormGroup } from '@blueprintjs/core';
+import React from 'react';
+
+export type FormJsonTabs = 'form' | 'json';
+
+export interface FormJsonSelectorProps {
+  tab: FormJsonTabs;
+  onChange: (tab: FormJsonTabs) => void;
+}
+
+export const FormJsonSelector = React.memo(function FormJsonSelector(props: FormJsonSelectorProps) {
+  const { tab, onChange } = props;
+
+  return (
+    <FormGroup className="form-json-selector">
+      <ButtonGroup fill>
+        <Button text="Form" active={tab === 'form'} onClick={() => onChange('form')} />
+        <Button text="JSON" active={tab === 'json'} onClick={() => onChange('json')} />
+      </ButtonGroup>
+    </FormGroup>
+  );
+});
diff --git a/web-console/src/dialogs/compaction-dialog/__snapshots__/compaction-dialog.spec.tsx.snap b/web-console/src/dialogs/compaction-dialog/__snapshots__/compaction-dialog.spec.tsx.snap
index 2b1635e..57ead47 100644
--- a/web-console/src/dialogs/compaction-dialog/__snapshots__/compaction-dialog.spec.tsx.snap
+++ b/web-console/src/dialogs/compaction-dialog/__snapshots__/compaction-dialog.spec.tsx.snap
@@ -8,24 +8,10 @@
   onClose={[Function]}
   title="Compaction config: test1"
 >
-  <Blueprint3.FormGroup
-    className="tabs"
-  >
-    <Blueprint3.ButtonGroup
-      fill={true}
-    >
-      <Blueprint3.Button
-        active={true}
-        onClick={[Function]}
-        text="Form"
-      />
-      <Blueprint3.Button
-        active={false}
-        onClick={[Function]}
-        text="JSON"
-      />
-    </Blueprint3.ButtonGroup>
-  </Blueprint3.FormGroup>
+  <Memo(FormJsonSelector)
+    onChange={[Function]}
+    tab="form"
+  />
   <div
     className="content"
   >
@@ -240,24 +226,10 @@
   onClose={[Function]}
   title="Compaction config: test1"
 >
-  <Blueprint3.FormGroup
-    className="tabs"
-  >
-    <Blueprint3.ButtonGroup
-      fill={true}
-    >
-      <Blueprint3.Button
-        active={true}
-        onClick={[Function]}
-        text="Form"
-      />
-      <Blueprint3.Button
-        active={false}
-        onClick={[Function]}
-        text="JSON"
-      />
-    </Blueprint3.ButtonGroup>
-  </Blueprint3.FormGroup>
+  <Memo(FormJsonSelector)
+    onChange={[Function]}
+    tab="form"
+  />
   <div
     className="content"
   >
@@ -472,24 +444,10 @@
   onClose={[Function]}
   title="Compaction config: test1"
 >
-  <Blueprint3.FormGroup
-    className="tabs"
-  >
-    <Blueprint3.ButtonGroup
-      fill={true}
-    >
-      <Blueprint3.Button
-        active={true}
-        onClick={[Function]}
-        text="Form"
-      />
-      <Blueprint3.Button
-        active={false}
-        onClick={[Function]}
-        text="JSON"
-      />
-    </Blueprint3.ButtonGroup>
-  </Blueprint3.FormGroup>
+  <Memo(FormJsonSelector)
+    onChange={[Function]}
+    tab="form"
+  />
   <div
     className="content"
   >
@@ -704,24 +662,10 @@
   onClose={[Function]}
   title="Compaction config: test1"
 >
-  <Blueprint3.FormGroup
-    className="tabs"
-  >
-    <Blueprint3.ButtonGroup
-      fill={true}
-    >
-      <Blueprint3.Button
-        active={true}
-        onClick={[Function]}
-        text="Form"
-      />
-      <Blueprint3.Button
-        active={false}
-        onClick={[Function]}
-        text="JSON"
-      />
-    </Blueprint3.ButtonGroup>
-  </Blueprint3.FormGroup>
+  <Memo(FormJsonSelector)
+    onChange={[Function]}
+    tab="form"
+  />
   <div
     className="content"
   >
diff --git a/web-console/src/dialogs/compaction-dialog/compaction-dialog.scss b/web-console/src/dialogs/compaction-dialog/compaction-dialog.scss
index 65606ba..f5cd57f 100644
--- a/web-console/src/dialogs/compaction-dialog/compaction-dialog.scss
+++ b/web-console/src/dialogs/compaction-dialog/compaction-dialog.scss
@@ -21,7 +21,7 @@
     height: 80vh;
   }
 
-  .tabs {
+  .form-json-selector {
     margin: 15px;
   }
 
diff --git a/web-console/src/dialogs/compaction-dialog/compaction-dialog.tsx b/web-console/src/dialogs/compaction-dialog/compaction-dialog.tsx
index 4a2611b..44bee9a 100644
--- a/web-console/src/dialogs/compaction-dialog/compaction-dialog.tsx
+++ b/web-console/src/dialogs/compaction-dialog/compaction-dialog.tsx
@@ -16,16 +16,18 @@
  * limitations under the License.
  */
 
-import { Button, ButtonGroup, Classes, Code, Dialog, FormGroup, Intent } from '@blueprintjs/core';
+import { Button, Classes, Code, Dialog, Intent } from '@blueprintjs/core';
 import React, { useState } from 'react';
 
 import { AutoForm, Field, JsonInput } from '../../components';
+import {
+  FormJsonSelector,
+  FormJsonTabs,
+} from '../../components/form-json-selector/form-json-selector';
 import { deepGet, deepSet } from '../../utils/object-change';
 
 import './compaction-dialog.scss';
 
-type Tabs = 'form' | 'json';
-
 type CompactionConfig = Record<string, any>;
 
 const COMPACTION_CONFIG_FIELDS: Field<CompactionConfig>[] = [
@@ -240,7 +242,7 @@
 export const CompactionDialog = React.memo(function CompactionDialog(props: CompactionDialogProps) {
   const { datasource, compactionConfig, onSave, onClose, onDelete } = props;
 
-  const [currentTab, setCurrentTab] = useState<Tabs>('form');
+  const [currentTab, setCurrentTab] = useState<FormJsonTabs>('form');
   const [currentConfig, setCurrentConfig] = useState<CompactionConfig>(
     compactionConfig || {
       dataSource: datasource,
@@ -261,20 +263,7 @@
       canOutsideClickClose={false}
       title={`Compaction config: ${datasource}`}
     >
-      <FormGroup className="tabs">
-        <ButtonGroup fill>
-          <Button
-            text="Form"
-            active={currentTab === 'form'}
-            onClick={() => setCurrentTab('form')}
-          />
-          <Button
-            text="JSON"
-            active={currentTab === 'json'}
-            onClick={() => setCurrentTab('json')}
-          />
-        </ButtonGroup>
-      </FormGroup>
+      <FormJsonSelector tab={currentTab} onChange={setCurrentTab} />
       <div className="content">
         {currentTab === 'form' ? (
           <AutoForm
diff --git a/web-console/src/dialogs/lookup-edit-dialog/__snapshots__/lookup-edit-dialog.spec.tsx.snap b/web-console/src/dialogs/lookup-edit-dialog/__snapshots__/lookup-edit-dialog.spec.tsx.snap
index 1fe5202..5349950 100644
--- a/web-console/src/dialogs/lookup-edit-dialog/__snapshots__/lookup-edit-dialog.spec.tsx.snap
+++ b/web-console/src/dialogs/lookup-edit-dialog/__snapshots__/lookup-edit-dialog.spec.tsx.snap
@@ -1,6 +1,6 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`lookup edit dialog matches snapshot 1`] = `
+exports[`LookupEditDialog matches snapshot 1`] = `
 <Blueprint3.Dialog
   canOutsideClickClose={true}
   className="lookup-edit-dialog"
@@ -8,405 +8,364 @@
   onClose={[Function]}
   title="Add lookup"
 >
-  <Blueprint3.FormGroup
-    className="lookup-label"
-    label="Name"
+  <div
+    className="content"
   >
-    <Blueprint3.InputGroup
-      disabled={false}
-      onChange={[Function]}
-      placeholder="Enter the lookup name"
-      value="test"
-    />
-  </Blueprint3.FormGroup>
-  <Blueprint3.FormGroup
-    className="lookup-label"
-    label="Tier"
-  >
-    <HTMLSelect
-      disabled={false}
-      onChange={[Function]}
-      value="test"
+    <Blueprint3.FormGroup
+      label="Name"
     >
-      <option
-        key="a"
-        value="a"
+      <Blueprint3.InputGroup
+        disabled={false}
+        onChange={[Function]}
+        placeholder="Enter the lookup name"
+        value="test"
+      />
+    </Blueprint3.FormGroup>
+    <Blueprint3.FormGroup
+      label="Tier"
+    >
+      <HTMLSelect
+        onChange={[Function]}
+        value="test"
       >
-        a
-      </option>
-      <option
-        key="b"
-        value="b"
-      >
-        b
-      </option>
-      <option
-        key="c"
-        value="c"
-      >
-        c
-      </option>
-      <option
-        key="d"
-        value="d"
-      >
-        d
-      </option>
-      <option
-        key="e"
-        value="e"
-      >
-        e
-      </option>
-      <option
-        key="f"
-        value="f"
-      >
-        f
-      </option>
-      <option
-        key="g"
-        value="g"
-      >
-        g
-      </option>
-      <option
-        key="h"
-        value="h"
-      >
-        h
-      </option>
-      <option
-        key="i"
-        value="i"
-      >
-        i
-      </option>
-      <option
-        key="j"
-        value="j"
-      >
-        j
-      </option>
-    </HTMLSelect>
-  </Blueprint3.FormGroup>
-  <Blueprint3.FormGroup
-    className="lookup-label"
-    label="Version"
-  >
-    <Blueprint3.InputGroup
+        <option
+          key="__default"
+          value="__default"
+        >
+          __default
+        </option>
+        <option
+          key="alt-tier"
+          value="alt-tier"
+        >
+          alt-tier
+        </option>
+      </HTMLSelect>
+    </Blueprint3.FormGroup>
+    <Blueprint3.FormGroup
+      label="Version"
+    >
+      <Blueprint3.InputGroup
+        onChange={[Function]}
+        placeholder="Enter the lookup version"
+        rightElement={
+          <Blueprint3.Button
+            minimal={true}
+            onClick={[Function]}
+            text="Use ISO as version"
+          />
+        }
+        value="test"
+      />
+    </Blueprint3.FormGroup>
+    <Memo(FormJsonSelector)
       onChange={[Function]}
-      placeholder="Enter the lookup version"
-      rightElement={
-        <Blueprint3.Button
-          minimal={true}
-          onClick={[Function]}
-          text="Use ISO as version"
-        />
-      }
-      value="test"
+      tab="form"
     />
-  </Blueprint3.FormGroup>
-  <AutoForm
-    fields={
-      Array [
-        Object {
-          "adjustment": [Function],
-          "name": "type",
-          "suggestions": Array [
-            "map",
-            "cachedNamespace",
-          ],
-          "type": "string",
-        },
-        Object {
-          "defined": [Function],
-          "name": "map",
-          "type": "json",
-        },
-        Object {
-          "defined": [Function],
-          "label": "Globally cached lookup type",
-          "name": "extractionNamespace.type",
-          "placeholder": "uri",
-          "suggestions": Array [
-            "uri",
-            "jdbc",
-          ],
-          "type": "string",
-        },
-        Object {
-          "defined": [Function],
-          "info": "A URI which specifies a directory (or other searchable resource) in which to search for files",
-          "label": "URI prefix",
-          "name": "extractionNamespace.uriPrefix",
-          "placeholder": "s3://bucket/some/key/prefix/",
-          "type": "string",
-        },
-        Object {
-          "defined": [Function],
-          "info": "Optional regex for matching the file name under uriPrefix. Only used if uriPrefix is used",
-          "label": "File regex",
-          "name": "extractionNamespace.fileRegex",
-          "placeholder": "(optional)",
-          "type": "string",
-        },
-        Object {
-          "defaultValue": "csv",
-          "defined": [Function],
-          "label": "Format",
-          "name": "extractionNamespace.namespaceParseSpec.format",
-          "suggestions": Array [
-            "csv",
-            "tsv",
-            "customJson",
-            "simpleJson",
-          ],
-          "type": "string",
-        },
-        Object {
-          "defined": [Function],
-          "info": "The list of columns in the csv file",
-          "label": "Columns",
-          "name": "extractionNamespace.namespaceParseSpec.columns",
-          "placeholder": "[\\"key\\", \\"value\\"]",
-          "type": "string-array",
-        },
-        Object {
-          "defined": [Function],
-          "info": "The name of the column containing the key",
-          "label": "Key column",
-          "name": "extractionNamespace.namespaceParseSpec.keyColumn",
-          "placeholder": "Key",
-          "type": "string",
-        },
-        Object {
-          "defined": [Function],
-          "info": "The name of the column containing the value",
-          "label": "Value column",
-          "name": "extractionNamespace.namespaceParseSpec.valueColumn",
-          "placeholder": "Value",
-          "type": "string",
-        },
-        Object {
-          "defaultValue": false,
-          "defined": [Function],
-          "info": "A flag to indicate that column information can be extracted from the input files' header row",
-          "label": "Has header row",
-          "name": "extractionNamespace.namespaceParseSpec.hasHeaderRow",
-          "type": "boolean",
-        },
-        Object {
-          "defined": [Function],
-          "info": "Number of header rows to be skipped. The default number of header rows to be skipped is 0.",
-          "label": "Skip header rows",
-          "name": "extractionNamespace.namespaceParseSpec.skipHeaderRows",
-          "placeholder": "(optional)",
-          "type": "number",
-        },
-        Object {
-          "defined": [Function],
-          "label": "Delimiter",
-          "name": "extractionNamespace.namespaceParseSpec.delimiter",
-          "placeholder": "(optional)",
-          "type": "string",
-        },
-        Object {
-          "defined": [Function],
-          "label": "List delimiter",
-          "name": "extractionNamespace.namespaceParseSpec.listDelimiter",
-          "placeholder": "(optional)",
-          "type": "string",
-        },
-        Object {
-          "defined": [Function],
-          "label": "Key field name",
-          "name": "extractionNamespace.namespaceParseSpec.keyFieldName",
-          "placeholder": "key",
-          "type": "string",
-        },
-        Object {
-          "defined": [Function],
-          "label": "Value field name",
-          "name": "extractionNamespace.namespaceParseSpec.valueFieldName",
-          "placeholder": "value",
-          "type": "string",
-        },
-        Object {
-          "defined": [Function],
-          "info": <React.Fragment>
-            <p>
-              The namespace value in the SQL query:
-            </p>
-            <p>
-              SELECT keyColumn, valueColumn, tsColumn? FROM 
-              <strong>
-                namespace
-              </strong>
-              .table WHERE filter
-            </p>
-          </React.Fragment>,
-          "label": "Namespace",
-          "name": "extractionNamespace.namespace",
-          "placeholder": "some_lookup",
-          "type": "string",
-        },
-        Object {
-          "defined": [Function],
-          "info": "Defines the connectURI value on the The connector config to used",
-          "label": "CreateTables",
-          "name": "extractionNamespace.connectorConfig.createTables",
-          "type": "boolean",
-        },
-        Object {
-          "defined": [Function],
-          "info": "Defines the connectURI value on the The connector config to used",
-          "label": "Connect URI",
-          "name": "extractionNamespace.connectorConfig.connectURI",
-          "type": "string",
-        },
-        Object {
-          "defined": [Function],
-          "info": "Defines the user to be used by the connector config",
-          "label": "User",
-          "name": "extractionNamespace.connectorConfig.user",
-          "type": "string",
-        },
-        Object {
-          "defined": [Function],
-          "info": "Defines the password to be used by the connector config",
-          "label": "Password",
-          "name": "extractionNamespace.connectorConfig.password",
-          "type": "string",
-        },
-        Object {
-          "defined": [Function],
-          "info": <React.Fragment>
-            <p>
-              The table which contains the key value pairs. This will become the table value in the SQL query:
-            </p>
-            <p>
-              SELECT keyColumn, valueColumn, tsColumn? FROM namespace.
-              <strong>
-                table
-              </strong>
-               WHERE filter
-            </p>
-          </React.Fragment>,
-          "label": "Table",
-          "name": "extractionNamespace.table",
-          "placeholder": "some_lookup_table",
-          "type": "string",
-        },
-        Object {
-          "defined": [Function],
-          "info": <React.Fragment>
-            <p>
-              The column in the table which contains the keys. This will become the keyColumn value in the SQL query:
-            </p>
-            <p>
-              SELECT 
-              <strong>
-                keyColumn
-              </strong>
-              , valueColumn, tsColumn? FROM namespace.table WHERE filter
-            </p>
-          </React.Fragment>,
-          "label": "Key column",
-          "name": "extractionNamespace.keyColumn",
-          "placeholder": "my_key_value",
-          "type": "string",
-        },
-        Object {
-          "defined": [Function],
-          "info": <React.Fragment>
-            <p>
-              The column in table which contains the values. This will become the valueColumn value in the SQL query:
-            </p>
-            <p>
-              SELECT keyColumn, 
-              <strong>
-                valueColumn
-              </strong>
-              , tsColumn? FROM namespace.table WHERE filter
-            </p>
-          </React.Fragment>,
-          "label": "Value column",
-          "name": "extractionNamespace.valueColumn",
-          "placeholder": "my_column_value",
-          "type": "string",
-        },
-        Object {
-          "defined": [Function],
-          "info": <React.Fragment>
-            <p>
-              The filter to be used when selecting lookups, this is used to create a where clause on lookup population. This will become the expression filter in the SQL query:
-            </p>
-            <p>
-              SELECT keyColumn, valueColumn, tsColumn? FROM namespace.table WHERE
-               
-              <strong>
-                filter
-              </strong>
-            </p>
-          </React.Fragment>,
-          "label": "Filter",
-          "name": "extractionNamespace.filter",
-          "placeholder": "(optional)",
-          "type": "string",
-        },
-        Object {
-          "defined": [Function],
-          "info": <React.Fragment>
-            <p>
-              The column in table which contains when the key was updated. This will become the Value in the SQL query:
-            </p>
-            <p>
-              SELECT keyColumn, valueColumn, 
-              <strong>
-                tsColumn
-              </strong>
-              ? FROM namespace.table WHERE filter
-            </p>
-          </React.Fragment>,
-          "label": "TsColumn",
-          "name": "extractionNamespace.tsColumn",
-          "placeholder": "(optional)",
-          "type": "string",
-        },
-        Object {
-          "defined": [Function],
-          "info": "Period between polling for updates",
-          "label": "Poll period",
-          "name": "extractionNamespace.pollPeriod",
-          "placeholder": "(optional)",
-          "type": "string",
-        },
-        Object {
-          "defined": [Function],
-          "info": "How long to wait (in ms) for the first run of the cache to populate. 0 indicates to not wait",
-          "label": "First cache timeout",
-          "name": "firstCacheTimeout",
-          "placeholder": "(optional)",
-          "type": "number",
-        },
-        Object {
-          "defaultValue": false,
-          "defined": [Function],
-          "info": "If the underlying map is injective (keys and values are unique) then optimizations can occur internally by setting this to true",
-          "name": "injective",
-          "type": "boolean",
-        },
-      ]
-    }
-    model={
-      Object {
-        "map": Object {},
-        "type": "map",
+    <AutoForm
+      fields={
+        Array [
+          Object {
+            "adjustment": [Function],
+            "name": "type",
+            "suggestions": Array [
+              "map",
+              "cachedNamespace",
+            ],
+            "type": "string",
+          },
+          Object {
+            "defined": [Function],
+            "height": "60vh",
+            "name": "map",
+            "type": "json",
+          },
+          Object {
+            "defined": [Function],
+            "label": "Globally cached lookup type",
+            "name": "extractionNamespace.type",
+            "placeholder": "uri",
+            "suggestions": Array [
+              "uri",
+              "jdbc",
+            ],
+            "type": "string",
+          },
+          Object {
+            "defined": [Function],
+            "info": "A URI which specifies a directory (or other searchable resource) in which to search for files",
+            "label": "URI prefix",
+            "name": "extractionNamespace.uriPrefix",
+            "placeholder": "s3://bucket/some/key/prefix/",
+            "type": "string",
+          },
+          Object {
+            "defined": [Function],
+            "info": "Optional regex for matching the file name under uriPrefix. Only used if uriPrefix is used",
+            "label": "File regex",
+            "name": "extractionNamespace.fileRegex",
+            "placeholder": "(optional)",
+            "type": "string",
+          },
+          Object {
+            "defaultValue": "csv",
+            "defined": [Function],
+            "label": "Format",
+            "name": "extractionNamespace.namespaceParseSpec.format",
+            "suggestions": Array [
+              "csv",
+              "tsv",
+              "customJson",
+              "simpleJson",
+            ],
+            "type": "string",
+          },
+          Object {
+            "defined": [Function],
+            "info": "The list of columns in the csv file",
+            "label": "Columns",
+            "name": "extractionNamespace.namespaceParseSpec.columns",
+            "placeholder": "[\\"key\\", \\"value\\"]",
+            "type": "string-array",
+          },
+          Object {
+            "defined": [Function],
+            "info": "The name of the column containing the key",
+            "label": "Key column",
+            "name": "extractionNamespace.namespaceParseSpec.keyColumn",
+            "placeholder": "Key",
+            "type": "string",
+          },
+          Object {
+            "defined": [Function],
+            "info": "The name of the column containing the value",
+            "label": "Value column",
+            "name": "extractionNamespace.namespaceParseSpec.valueColumn",
+            "placeholder": "Value",
+            "type": "string",
+          },
+          Object {
+            "defaultValue": false,
+            "defined": [Function],
+            "info": "A flag to indicate that column information can be extracted from the input files' header row",
+            "label": "Has header row",
+            "name": "extractionNamespace.namespaceParseSpec.hasHeaderRow",
+            "type": "boolean",
+          },
+          Object {
+            "defined": [Function],
+            "info": "Number of header rows to be skipped. The default number of header rows to be skipped is 0.",
+            "label": "Skip header rows",
+            "name": "extractionNamespace.namespaceParseSpec.skipHeaderRows",
+            "placeholder": "(optional)",
+            "type": "number",
+          },
+          Object {
+            "defined": [Function],
+            "label": "Delimiter",
+            "name": "extractionNamespace.namespaceParseSpec.delimiter",
+            "placeholder": "(optional)",
+            "type": "string",
+          },
+          Object {
+            "defined": [Function],
+            "label": "List delimiter",
+            "name": "extractionNamespace.namespaceParseSpec.listDelimiter",
+            "placeholder": "(optional)",
+            "type": "string",
+          },
+          Object {
+            "defined": [Function],
+            "label": "Key field name",
+            "name": "extractionNamespace.namespaceParseSpec.keyFieldName",
+            "placeholder": "key",
+            "type": "string",
+          },
+          Object {
+            "defined": [Function],
+            "label": "Value field name",
+            "name": "extractionNamespace.namespaceParseSpec.valueFieldName",
+            "placeholder": "value",
+            "type": "string",
+          },
+          Object {
+            "defined": [Function],
+            "info": <React.Fragment>
+              <p>
+                The namespace value in the SQL query:
+              </p>
+              <p>
+                SELECT keyColumn, valueColumn, tsColumn? FROM 
+                <strong>
+                  namespace
+                </strong>
+                .table WHERE filter
+              </p>
+            </React.Fragment>,
+            "label": "Namespace",
+            "name": "extractionNamespace.namespace",
+            "placeholder": "some_lookup",
+            "type": "string",
+          },
+          Object {
+            "defined": [Function],
+            "info": "Defines the connectURI value on the The connector config to used",
+            "label": "CreateTables",
+            "name": "extractionNamespace.connectorConfig.createTables",
+            "type": "boolean",
+          },
+          Object {
+            "defined": [Function],
+            "info": "Defines the connectURI value on the The connector config to used",
+            "label": "Connect URI",
+            "name": "extractionNamespace.connectorConfig.connectURI",
+            "type": "string",
+          },
+          Object {
+            "defined": [Function],
+            "info": "Defines the user to be used by the connector config",
+            "label": "User",
+            "name": "extractionNamespace.connectorConfig.user",
+            "type": "string",
+          },
+          Object {
+            "defined": [Function],
+            "info": "Defines the password to be used by the connector config",
+            "label": "Password",
+            "name": "extractionNamespace.connectorConfig.password",
+            "type": "string",
+          },
+          Object {
+            "defined": [Function],
+            "info": <React.Fragment>
+              <p>
+                The table which contains the key value pairs. This will become the table value in the SQL query:
+              </p>
+              <p>
+                SELECT keyColumn, valueColumn, tsColumn? FROM namespace.
+                <strong>
+                  table
+                </strong>
+                 WHERE filter
+              </p>
+            </React.Fragment>,
+            "label": "Table",
+            "name": "extractionNamespace.table",
+            "placeholder": "some_lookup_table",
+            "type": "string",
+          },
+          Object {
+            "defined": [Function],
+            "info": <React.Fragment>
+              <p>
+                The column in the table which contains the keys. This will become the keyColumn value in the SQL query:
+              </p>
+              <p>
+                SELECT 
+                <strong>
+                  keyColumn
+                </strong>
+                , valueColumn, tsColumn? FROM namespace.table WHERE filter
+              </p>
+            </React.Fragment>,
+            "label": "Key column",
+            "name": "extractionNamespace.keyColumn",
+            "placeholder": "my_key_value",
+            "type": "string",
+          },
+          Object {
+            "defined": [Function],
+            "info": <React.Fragment>
+              <p>
+                The column in table which contains the values. This will become the valueColumn value in the SQL query:
+              </p>
+              <p>
+                SELECT keyColumn, 
+                <strong>
+                  valueColumn
+                </strong>
+                , tsColumn? FROM namespace.table WHERE filter
+              </p>
+            </React.Fragment>,
+            "label": "Value column",
+            "name": "extractionNamespace.valueColumn",
+            "placeholder": "my_column_value",
+            "type": "string",
+          },
+          Object {
+            "defined": [Function],
+            "info": <React.Fragment>
+              <p>
+                The filter to be used when selecting lookups, this is used to create a where clause on lookup population. This will become the expression filter in the SQL query:
+              </p>
+              <p>
+                SELECT keyColumn, valueColumn, tsColumn? FROM namespace.table WHERE
+                 
+                <strong>
+                  filter
+                </strong>
+              </p>
+            </React.Fragment>,
+            "label": "Filter",
+            "name": "extractionNamespace.filter",
+            "placeholder": "(optional)",
+            "type": "string",
+          },
+          Object {
+            "defined": [Function],
+            "info": <React.Fragment>
+              <p>
+                The column in table which contains when the key was updated. This will become the Value in the SQL query:
+              </p>
+              <p>
+                SELECT keyColumn, valueColumn, 
+                <strong>
+                  tsColumn
+                </strong>
+                ? FROM namespace.table WHERE filter
+              </p>
+            </React.Fragment>,
+            "label": "TsColumn",
+            "name": "extractionNamespace.tsColumn",
+            "placeholder": "(optional)",
+            "type": "string",
+          },
+          Object {
+            "defined": [Function],
+            "info": "Period between polling for updates",
+            "label": "Poll period",
+            "name": "extractionNamespace.pollPeriod",
+            "placeholder": "(optional)",
+            "type": "string",
+          },
+          Object {
+            "defined": [Function],
+            "info": "How long to wait (in ms) for the first run of the cache to populate. 0 indicates to not wait",
+            "label": "First cache timeout",
+            "name": "firstCacheTimeout",
+            "placeholder": "(optional)",
+            "type": "number",
+          },
+          Object {
+            "defaultValue": false,
+            "defined": [Function],
+            "info": "If the underlying map is injective (keys and values are unique) then optimizations can occur internally by setting this to true",
+            "name": "injective",
+            "type": "boolean",
+          },
+        ]
       }
-    }
-    onChange={[Function]}
-  />
+      model={
+        Object {
+          "map": Object {
+            "a": 1,
+          },
+          "type": "map",
+        }
+      }
+      onChange={[Function]}
+    />
+  </div>
   <div
     className="bp3-dialog-footer"
   >
diff --git a/web-console/src/dialogs/lookup-edit-dialog/lookup-edit-dialog.scss b/web-console/src/dialogs/lookup-edit-dialog/lookup-edit-dialog.scss
index 7ee469b..e42914f 100644
--- a/web-console/src/dialogs/lookup-edit-dialog/lookup-edit-dialog.scss
+++ b/web-console/src/dialogs/lookup-edit-dialog/lookup-edit-dialog.scss
@@ -18,19 +18,15 @@
 
 .lookup-edit-dialog {
   &.bp3-dialog {
-    top: 10vh;
-
+    height: 80vh;
     width: 600px;
   }
 
-  .auto-form {
-    margin: 5px 20px 10px;
-  }
-
-  .lookup-label {
-    padding: 0 20px;
-    margin-top: 5px;
-    margin-bottom: 5px;
+  .content {
+    margin: 0 15px 10px 0;
+    padding: 15px 5px 5px 15px;
+    flex: 1;
+    overflow: auto;
   }
 
   .ace-solarized-dark {
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 a314302..f9eebd8 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
@@ -21,7 +21,7 @@
 
 import { isLookupSubmitDisabled, LookupEditDialog } from './lookup-edit-dialog';
 
-describe('lookup edit dialog', () => {
+describe('LookupEditDialog', () => {
   it('matches snapshot', () => {
     const lookupEditDialog = shallow(
       <LookupEditDialog
@@ -31,9 +31,9 @@
         lookupName={'test'}
         lookupTier={'test'}
         lookupVersion={'test'}
-        lookupSpec={{ type: 'map', map: {} }}
+        lookupSpec={{ type: 'map', map: { a: 1 } }}
         isEdit={false}
-        allLookupTiers={['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']}
+        allLookupTiers={['__default', 'alt-tier']}
       />,
     );
 
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 fe203fd..24eb832 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
@@ -25,9 +25,13 @@
   InputGroup,
   Intent,
 } from '@blueprintjs/core';
-import React from 'react';
+import React, { useState } from 'react';
 
-import { AutoForm, Field } from '../../components';
+import { AutoForm, Field, JsonInput } from '../../components';
+import {
+  FormJsonSelector,
+  FormJsonTabs,
+} from '../../components/form-json-selector/form-json-selector';
 
 import './lookup-edit-dialog.scss';
 
@@ -158,9 +162,8 @@
   {
     name: 'map',
     type: 'json',
-    defined: (model: LookupSpec) => {
-      return model.type === 'map';
-    },
+    height: '60vh',
+    defined: (model: LookupSpec) => model.type === 'map',
   },
   {
     name: 'extractionNamespace.type',
@@ -552,47 +555,8 @@
     isEdit,
     allLookupTiers,
   } = props;
-
-  let updateVersionOnSubmit = true;
-
-  function addISOVersion() {
-    const currentDate = new Date();
-    const ISOString = currentDate.toISOString();
-    onChange('version', ISOString);
-  }
-
-  function renderTierInput() {
-    if (isEdit) {
-      return (
-        <FormGroup className="lookup-label" label="Tier">
-          <InputGroup
-            value={lookupTier}
-            onChange={(e: any) => {
-              updateVersionOnSubmit = false;
-              onChange('tier', e.target.value);
-            }}
-            disabled
-          />
-        </FormGroup>
-      );
-    } else {
-      return (
-        <FormGroup className="lookup-label" label="Tier">
-          <HTMLSelect
-            disabled={isEdit}
-            value={lookupTier}
-            onChange={(e: any) => onChange('tier', e.target.value)}
-          >
-            {allLookupTiers.map(tier => (
-              <option key={tier} value={tier}>
-                {tier}
-              </option>
-            ))}
-          </HTMLSelect>
-        </FormGroup>
-      );
-    }
-  }
+  const [currentTab, setCurrentTab] = useState<FormJsonTabs>('form');
+  const [updateVersionOnSubmit, setUpdateVersionOnSubmit] = useState(true);
 
   return (
     <Dialog
@@ -601,32 +565,68 @@
       onClose={onClose}
       title={isEdit ? 'Edit lookup' : 'Add lookup'}
     >
-      <FormGroup className="lookup-label" label="Name">
-        <InputGroup
-          value={lookupName}
-          onChange={(e: any) => onChange('name', e.target.value)}
-          disabled={isEdit}
-          placeholder="Enter the lookup name"
-        />
-      </FormGroup>
-      {renderTierInput()}
-      <FormGroup className="lookup-label" label="Version">
-        <InputGroup
-          value={lookupVersion}
-          onChange={(e: any) => onChange('version', e.target.value)}
-          placeholder="Enter the lookup version"
-          rightElement={
-            <Button minimal text="Use ISO as version" onClick={() => addISOVersion()} />
-          }
-        />
-      </FormGroup>
-      <AutoForm
-        fields={LOOKUP_FIELDS}
-        model={lookupSpec}
-        onChange={m => {
-          onChange('spec', m);
-        }}
-      />
+      <div className="content">
+        <FormGroup label="Name">
+          <InputGroup
+            value={lookupName}
+            onChange={(e: any) => onChange('name', e.target.value)}
+            disabled={isEdit}
+            placeholder="Enter the lookup name"
+          />
+        </FormGroup>
+        <FormGroup label="Tier">
+          {isEdit ? (
+            <InputGroup
+              value={lookupTier}
+              onChange={(e: any) => onChange('tier', e.target.value)}
+              disabled
+            />
+          ) : (
+            <HTMLSelect value={lookupTier} onChange={(e: any) => onChange('tier', e.target.value)}>
+              {allLookupTiers.map(tier => (
+                <option key={tier} value={tier}>
+                  {tier}
+                </option>
+              ))}
+            </HTMLSelect>
+          )}
+        </FormGroup>
+        <FormGroup label="Version">
+          <InputGroup
+            value={lookupVersion}
+            onChange={(e: any) => {
+              setUpdateVersionOnSubmit(false);
+              onChange('version', e.target.value);
+            }}
+            placeholder="Enter the lookup version"
+            rightElement={
+              <Button
+                minimal
+                text="Use ISO as version"
+                onClick={() => onChange('version', new Date().toISOString())}
+              />
+            }
+          />
+        </FormGroup>
+        <FormJsonSelector tab={currentTab} onChange={setCurrentTab} />
+        {currentTab === 'form' ? (
+          <AutoForm
+            fields={LOOKUP_FIELDS}
+            model={lookupSpec}
+            onChange={m => {
+              onChange('spec', m);
+            }}
+          />
+        ) : (
+          <JsonInput
+            value={lookupSpec}
+            onChange={m => {
+              onChange('spec', m);
+            }}
+            height="80vh"
+          />
+        )}
+      </div>
       <div className={Classes.DIALOG_FOOTER}>
         <div className={Classes.DIALOG_FOOTER_ACTIONS}>
           <Button text="Close" onClick={onClose} />
diff --git a/web-console/src/views/datasource-view/datasource-view.tsx b/web-console/src/views/datasource-view/datasource-view.tsx
index 9f81a03..02397c6 100644
--- a/web-console/src/views/datasource-view/datasource-view.tsx
+++ b/web-console/src/views/datasource-view/datasource-view.tsx
@@ -893,9 +893,10 @@
           data={datasources}
           loading={datasourcesAndDefaultRulesState.loading}
           noDataText={
-            !datasourcesAndDefaultRulesState.loading && datasources && !datasources.length
+            datasourcesAndDefaultRulesState.getErrorMessage() ||
+            (!datasourcesAndDefaultRulesState.loading && datasources && !datasources.length
               ? 'No datasources'
-              : datasourcesAndDefaultRulesState.getErrorMessage() || ''
+              : '')
           }
           filterable
           filtered={datasourceFilter}
diff --git a/web-console/src/views/lookups-view/lookups-view.tsx b/web-console/src/views/lookups-view/lookups-view.tsx
index 46eb421..3666263 100644
--- a/web-console/src/views/lookups-view/lookups-view.tsx
+++ b/web-console/src/views/lookups-view/lookups-view.tsx
@@ -203,11 +203,11 @@
     }));
   };
 
-  private async submitLookupEdit(updatelookupEditVersion: boolean) {
+  private async submitLookupEdit(updateLookupVersion: boolean) {
     const { lookupEdit, isEdit } = this.state;
     if (!lookupEdit) return;
 
-    const version = updatelookupEditVersion ? new Date().toISOString() : lookupEdit.version;
+    const version = updateLookupVersion ? new Date().toISOString() : lookupEdit.version;
     let endpoint = '/druid/coordinator/v1/lookups/config';
     const specJson: any = lookupEdit.spec;
     let dataJson: any;