mirror of https://github.com/apache/druid.git
Use auto-form for add an edit lookups (#9587)
* use auto form * jest -u * fix unreachable statment * complete the owl * jest -u * remove changes to query-view * fix permissions * add test, fix info * add cool highlights * fix formatting * fix capitalization * add optional placeholder * add space
This commit is contained in:
parent
2b2b9efcd7
commit
6f3d403491
|
@ -58,7 +58,7 @@ exports[`lookup edit dialog matches snapshot 1`] = `
|
||||||
<label
|
<label
|
||||||
class="bp3-label"
|
class="bp3-label"
|
||||||
>
|
>
|
||||||
Name:
|
Name
|
||||||
|
|
||||||
<span
|
<span
|
||||||
class="bp3-text-muted"
|
class="bp3-text-muted"
|
||||||
|
@ -86,7 +86,7 @@ exports[`lookup edit dialog matches snapshot 1`] = `
|
||||||
<label
|
<label
|
||||||
class="bp3-label"
|
class="bp3-label"
|
||||||
>
|
>
|
||||||
Tier:
|
Tier
|
||||||
|
|
||||||
<span
|
<span
|
||||||
class="bp3-text-muted"
|
class="bp3-text-muted"
|
||||||
|
@ -178,7 +178,7 @@ exports[`lookup edit dialog matches snapshot 1`] = `
|
||||||
<label
|
<label
|
||||||
class="bp3-label"
|
class="bp3-label"
|
||||||
>
|
>
|
||||||
Version:
|
Version
|
||||||
|
|
||||||
<span
|
<span
|
||||||
class="bp3-text-muted"
|
class="bp3-text-muted"
|
||||||
|
@ -215,107 +215,177 @@ exports[`lookup edit dialog matches snapshot 1`] = `
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="bp3-form-group lookup-label"
|
class="auto-form"
|
||||||
>
|
>
|
||||||
<label
|
|
||||||
class="bp3-label"
|
|
||||||
>
|
|
||||||
Spec:
|
|
||||||
|
|
||||||
<span
|
|
||||||
class="bp3-text-muted"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div
|
<div
|
||||||
class="bp3-form-content"
|
class="bp3-form-group form-group-with-info"
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class=" ace_editor ace-tm lookup-edit-dialog-textarea"
|
|
||||||
id="brace-editor"
|
|
||||||
style="width: auto; height: 40vh;"
|
|
||||||
>
|
|
||||||
<textarea
|
|
||||||
autocapitalize="off"
|
|
||||||
autocorrect="off"
|
|
||||||
class="ace_text-input"
|
|
||||||
spellcheck="false"
|
|
||||||
style="opacity: 0;"
|
|
||||||
wrap="off"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
aria-hidden="true"
|
|
||||||
class="ace_gutter"
|
|
||||||
style="display: none;"
|
|
||||||
>
|
>
|
||||||
|
<label
|
||||||
|
class="bp3-label"
|
||||||
|
>
|
||||||
|
Type
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="bp3-text-muted"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
<div
|
<div
|
||||||
class="ace_layer ace_gutter-layer ace_folding-enabled"
|
class="bp3-form-content"
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="ace_gutter-active-line"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ace_scroller"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ace_content"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ace_layer ace_print-margin-layer"
|
class="bp3-input-group suggestible-input"
|
||||||
>
|
>
|
||||||
<div
|
<input
|
||||||
class="ace_print-margin"
|
class="bp3-input"
|
||||||
style="left: 4px; visibility: hidden;"
|
placeholder=""
|
||||||
/>
|
style="padding-right: 0px;"
|
||||||
</div>
|
suggestions="map,cachedNamespace"
|
||||||
<div
|
type="text"
|
||||||
class="ace_layer ace_marker-layer"
|
value="map"
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="ace_layer ace_text-layer"
|
|
||||||
style="padding: 0px 4px;"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="ace_layer ace_marker-layer"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="ace_layer ace_cursor-layer ace_hidden-cursors"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ace_cursor"
|
|
||||||
/>
|
/>
|
||||||
|
<span
|
||||||
|
class="bp3-input-action"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="bp3-popover-wrapper"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="bp3-popover-target"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="bp3-button bp3-minimal"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="bp3-icon bp3-icon-caret-down"
|
||||||
|
icon="caret-down"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
data-icon="caret-down"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
width="16"
|
||||||
|
>
|
||||||
|
<desc>
|
||||||
|
caret-down
|
||||||
|
</desc>
|
||||||
|
<path
|
||||||
|
d="M12 6.5c0-.28-.22-.5-.5-.5h-7a.495.495 0 00-.37.83l3.5 4c.09.1.22.17.37.17s.28-.07.37-.17l3.5-4c.08-.09.13-.2.13-.33z"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ace_scrollbar ace_scrollbar-v"
|
class="bp3-form-group form-group-with-info"
|
||||||
style="display: none; width: 20px;"
|
|
||||||
>
|
>
|
||||||
<div
|
<label
|
||||||
class="ace_scrollbar-inner"
|
class="bp3-label"
|
||||||
style="width: 20px;"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ace_scrollbar ace_scrollbar-h"
|
|
||||||
style="display: none; height: 20px;"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ace_scrollbar-inner"
|
|
||||||
style="height: 20px;"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style="height: auto; width: auto; top: 0px; left: 0px; visibility: hidden; position: absolute; white-space: pre; overflow: hidden;"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style="height: auto; width: auto; top: 0px; left: 0px; visibility: hidden; position: absolute; white-space: pre; overflow: visible;"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
style="height: auto; width: auto; top: 0px; left: 0px; visibility: hidden; position: absolute; white-space: pre; overflow: visible;"
|
|
||||||
>
|
>
|
||||||
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
Map
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="bp3-text-muted"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="bp3-form-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class=" ace_editor ace-tm json-input"
|
||||||
|
id="brace-editor"
|
||||||
|
style="width: 100%; height: 8vh;"
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
autocapitalize="off"
|
||||||
|
autocorrect="off"
|
||||||
|
class="ace_text-input"
|
||||||
|
spellcheck="false"
|
||||||
|
style="opacity: 0;"
|
||||||
|
wrap="off"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ace_gutter"
|
||||||
|
style="display: none;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ace_layer ace_gutter-layer ace_folding-enabled"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="ace_gutter-active-line"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ace_scroller"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ace_content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ace_layer ace_print-margin-layer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ace_print-margin"
|
||||||
|
style="left: 4px; visibility: hidden;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ace_layer ace_marker-layer"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="ace_layer ace_text-layer"
|
||||||
|
style="padding: 0px 4px;"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="ace_layer ace_marker-layer"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="ace_layer ace_cursor-layer ace_hidden-cursors"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ace_cursor"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ace_scrollbar ace_scrollbar-v"
|
||||||
|
style="display: none; width: 20px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ace_scrollbar-inner"
|
||||||
|
style="width: 20px;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ace_scrollbar ace_scrollbar-h"
|
||||||
|
style="display: none; height: 20px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ace_scrollbar-inner"
|
||||||
|
style="height: 20px;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="height: auto; width: auto; top: 0px; left: 0px; visibility: hidden; position: absolute; white-space: pre; overflow: hidden;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="height: auto; width: auto; top: 0px; left: 0px; visibility: hidden; position: absolute; white-space: pre; overflow: visible;"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style="height: auto; width: auto; top: 0px; left: 0px; visibility: hidden; position: absolute; white-space: pre; overflow: visible;"
|
||||||
|
>
|
||||||
|
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -336,9 +406,7 @@ exports[`lookup edit dialog matches snapshot 1`] = `
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="bp3-button bp3-disabled bp3-intent-primary"
|
class="bp3-button bp3-intent-primary"
|
||||||
disabled=""
|
|
||||||
tabindex="-1"
|
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
|
|
@ -23,8 +23,8 @@
|
||||||
width: 600px;
|
width: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ace_editor {
|
.auto-form {
|
||||||
margin: 0px 20px 10px;
|
margin: 5px 20px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lookup-label {
|
.lookup-label {
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { LookupEditDialog } from './lookup-edit-dialog';
|
import { isDisabled, LookupEditDialog } from './lookup-edit-dialog';
|
||||||
|
|
||||||
describe('lookup edit dialog', () => {
|
describe('lookup edit dialog', () => {
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
|
@ -31,7 +31,7 @@ describe('lookup edit dialog', () => {
|
||||||
lookupName={'test'}
|
lookupName={'test'}
|
||||||
lookupTier={'test'}
|
lookupTier={'test'}
|
||||||
lookupVersion={'test'}
|
lookupVersion={'test'}
|
||||||
lookupSpec={'test'}
|
lookupSpec={{ type: 'map', map: {} }}
|
||||||
isEdit={false}
|
isEdit={false}
|
||||||
allLookupTiers={['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']}
|
allLookupTiers={['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']}
|
||||||
/>
|
/>
|
||||||
|
@ -41,3 +41,433 @@ describe('lookup edit dialog', () => {
|
||||||
expect(document.body.lastChild).toMatchSnapshot();
|
expect(document.body.lastChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Type Map Should be disabled', () => {
|
||||||
|
it('Missing LookupName', () => {
|
||||||
|
expect(isDisabled(undefined, 'v1', '__default', { type: '' })).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Empty version', () => {
|
||||||
|
expect(isDisabled('lookup', '', '__default', { type: '' })).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Missing version', () => {
|
||||||
|
expect(isDisabled('lookup', undefined, '__default', { type: '' })).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Empty tier', () => {
|
||||||
|
expect(isDisabled('lookup', 'v1', '', { type: '' })).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Missing tier', () => {
|
||||||
|
expect(isDisabled('lookup', 'v1', undefined, { type: '' })).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Missing spec', () => {
|
||||||
|
expect(isDisabled('lookup', 'v1', '__default', {})).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Type undefined', () => {
|
||||||
|
expect(isDisabled('lookup', 'v1', '__default', { type: undefined })).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Lookup of type map with no map', () => {
|
||||||
|
expect(isDisabled('lookup', 'v1', '__default', { type: 'map' })).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Lookup of type cachedNamespace with no extractionNamespace', () => {
|
||||||
|
expect(isDisabled('lookup', 'v1', '__default', { type: 'cachedNamespace' })).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Lookup of type cachedNamespace with extractionNamespace type uri, format csv, no namespaceParseSpec', () => {
|
||||||
|
expect(
|
||||||
|
isDisabled('lookup', 'v1', '__default', {
|
||||||
|
type: 'cachedNamespace',
|
||||||
|
extractionNamespace: {
|
||||||
|
type: 'uri',
|
||||||
|
uriPrefix: 's3://bucket/some/key/prefix/',
|
||||||
|
fileRegex: 'renames-[0-9]*\\.gz',
|
||||||
|
pollPeriod: 'PT5M',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Lookup of type cachedNamespace with extractionNamespace type uri, format csv, no columns and skipHeaderRows', () => {
|
||||||
|
expect(
|
||||||
|
isDisabled('lookup', 'v1', '__default', {
|
||||||
|
type: 'cachedNamespace',
|
||||||
|
extractionNamespace: {
|
||||||
|
type: 'uri',
|
||||||
|
uriPrefix: 's3://bucket/some/key/prefix/',
|
||||||
|
fileRegex: 'renames-[0-9]*\\.gz',
|
||||||
|
namespaceParseSpec: {
|
||||||
|
format: 'csv',
|
||||||
|
},
|
||||||
|
pollPeriod: 'PT5M',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Lookup of type cachedNamespace with extractionNamespace type uri, format tsv, no columns', () => {
|
||||||
|
expect(
|
||||||
|
isDisabled('lookup', 'v1', '__default', {
|
||||||
|
type: 'cachedNamespace',
|
||||||
|
extractionNamespace: {
|
||||||
|
type: 'uri',
|
||||||
|
uriPrefix: 's3://bucket/some/key/prefix/',
|
||||||
|
fileRegex: 'renames-[0-9]*\\.gz',
|
||||||
|
namespaceParseSpec: {
|
||||||
|
format: 'tsv',
|
||||||
|
skipHeaderRows: 0,
|
||||||
|
},
|
||||||
|
pollPeriod: 'PT5M',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Lookup of type cachedNamespace with extractionNamespace type customJson, format tsv, no keyFieldName', () => {
|
||||||
|
expect(
|
||||||
|
isDisabled('lookup', 'v1', '__default', {
|
||||||
|
type: 'cachedNamespace',
|
||||||
|
extractionNamespace: {
|
||||||
|
type: 'uri',
|
||||||
|
uriPrefix: 's3://bucket/some/key/prefix/',
|
||||||
|
fileRegex: 'renames-[0-9]*\\.gz',
|
||||||
|
namespaceParseSpec: {
|
||||||
|
format: 'customJson',
|
||||||
|
valueFieldName: 'value',
|
||||||
|
},
|
||||||
|
pollPeriod: 'PT5M',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Lookup of type cachedNamespace with extractionNamespace type customJson, format customJson, no valueFieldName', () => {
|
||||||
|
expect(
|
||||||
|
isDisabled('lookup', 'v1', '__default', {
|
||||||
|
type: 'cachedNamespace',
|
||||||
|
extractionNamespace: {
|
||||||
|
type: 'uri',
|
||||||
|
uriPrefix: 's3://bucket/some/key/prefix/',
|
||||||
|
fileRegex: 'renames-[0-9]*\\.gz',
|
||||||
|
namespaceParseSpec: {
|
||||||
|
format: 'customJson',
|
||||||
|
keyFieldName: 'key',
|
||||||
|
},
|
||||||
|
pollPeriod: 'PT5M',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Type cachedNamespace should be disabled', () => {
|
||||||
|
it('No extractionNamespace', () => {
|
||||||
|
expect(isDisabled('lookup', 'v1', '__default', { type: 'cachedNamespace' })).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ExtractionNamespace type URI', () => {
|
||||||
|
it('Format csv, no namespaceParseSpec', () => {
|
||||||
|
expect(
|
||||||
|
isDisabled('lookup', 'v1', '__default', {
|
||||||
|
type: 'cachedNamespace',
|
||||||
|
extractionNamespace: {
|
||||||
|
type: 'uri',
|
||||||
|
uriPrefix: 's3://bucket/some/key/prefix/',
|
||||||
|
fileRegex: 'renames-[0-9]*\\.gz',
|
||||||
|
pollPeriod: 'PT5M',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Format csv, no columns and skipHeaderRows', () => {
|
||||||
|
expect(
|
||||||
|
isDisabled('lookup', 'v1', '__default', {
|
||||||
|
type: 'cachedNamespace',
|
||||||
|
extractionNamespace: {
|
||||||
|
type: 'uri',
|
||||||
|
uriPrefix: 's3://bucket/some/key/prefix/',
|
||||||
|
fileRegex: 'renames-[0-9]*\\.gz',
|
||||||
|
namespaceParseSpec: {
|
||||||
|
format: 'csv',
|
||||||
|
},
|
||||||
|
pollPeriod: 'PT5M',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Format tsv, no columns', () => {
|
||||||
|
expect(
|
||||||
|
isDisabled('lookup', 'v1', '__default', {
|
||||||
|
type: 'cachedNamespace',
|
||||||
|
extractionNamespace: {
|
||||||
|
type: 'uri',
|
||||||
|
uriPrefix: 's3://bucket/some/key/prefix/',
|
||||||
|
fileRegex: 'renames-[0-9]*\\.gz',
|
||||||
|
namespaceParseSpec: {
|
||||||
|
format: 'tsv',
|
||||||
|
skipHeaderRows: 0,
|
||||||
|
},
|
||||||
|
pollPeriod: 'PT5M',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Format tsv, no keyFieldName', () => {
|
||||||
|
expect(
|
||||||
|
isDisabled('lookup', 'v1', '__default', {
|
||||||
|
type: 'cachedNamespace',
|
||||||
|
extractionNamespace: {
|
||||||
|
type: 'uri',
|
||||||
|
uriPrefix: 's3://bucket/some/key/prefix/',
|
||||||
|
fileRegex: 'renames-[0-9]*\\.gz',
|
||||||
|
namespaceParseSpec: {
|
||||||
|
format: 'customJson',
|
||||||
|
valueFieldName: 'value',
|
||||||
|
},
|
||||||
|
pollPeriod: 'PT5M',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Format customJson, no valueFieldName', () => {
|
||||||
|
expect(
|
||||||
|
isDisabled('lookup', 'v1', '__default', {
|
||||||
|
type: 'cachedNamespace',
|
||||||
|
extractionNamespace: {
|
||||||
|
type: 'uri',
|
||||||
|
uriPrefix: 's3://bucket/some/key/prefix/',
|
||||||
|
fileRegex: 'renames-[0-9]*\\.gz',
|
||||||
|
namespaceParseSpec: {
|
||||||
|
format: 'customJson',
|
||||||
|
keyFieldName: 'key',
|
||||||
|
},
|
||||||
|
pollPeriod: 'PT5M',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ExtractionNamespace type JDBC', () => {
|
||||||
|
it('No namespace', () => {
|
||||||
|
expect(
|
||||||
|
isDisabled('lookup', 'v1', '__default', {
|
||||||
|
type: 'cachedNamespace',
|
||||||
|
extractionNamespace: {
|
||||||
|
type: 'jdbc',
|
||||||
|
namespace: undefined,
|
||||||
|
connectorConfig: {
|
||||||
|
createTables: true,
|
||||||
|
connectURI: 'jdbc:mysql://localhost:3306/druid',
|
||||||
|
user: 'druid',
|
||||||
|
password: 'diurd',
|
||||||
|
},
|
||||||
|
table: 'some_lookup_table',
|
||||||
|
keyColumn: 'the_old_dim_value',
|
||||||
|
valueColumn: 'the_new_dim_value',
|
||||||
|
tsColumn: 'timestamp_column',
|
||||||
|
pollPeriod: 600000,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('No connectorConfig', () => {
|
||||||
|
expect(
|
||||||
|
isDisabled('lookup', 'v1', '__default', {
|
||||||
|
type: 'cachedNamespace',
|
||||||
|
extractionNamespace: {
|
||||||
|
type: 'jdbc',
|
||||||
|
namespace: 'some_lookup',
|
||||||
|
connectorConfig: undefined,
|
||||||
|
table: 'some_lookup_table',
|
||||||
|
keyColumn: 'the_old_dim_value',
|
||||||
|
valueColumn: 'the_new_dim_value',
|
||||||
|
tsColumn: 'timestamp_column',
|
||||||
|
pollPeriod: 600000,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('No table', () => {
|
||||||
|
expect(
|
||||||
|
isDisabled('lookup', 'v1', '__default', {
|
||||||
|
type: 'cachedNamespace',
|
||||||
|
extractionNamespace: {
|
||||||
|
type: 'jdbc',
|
||||||
|
namespace: 'some_lookup',
|
||||||
|
connectorConfig: {
|
||||||
|
createTables: true,
|
||||||
|
connectURI: 'jdbc:mysql://localhost:3306/druid',
|
||||||
|
user: 'druid',
|
||||||
|
password: 'diurd',
|
||||||
|
},
|
||||||
|
table: undefined,
|
||||||
|
keyColumn: 'the_old_dim_value',
|
||||||
|
valueColumn: 'the_new_dim_value',
|
||||||
|
tsColumn: 'timestamp_column',
|
||||||
|
pollPeriod: 600000,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('No keyColumn', () => {
|
||||||
|
expect(
|
||||||
|
isDisabled('lookup', 'v1', '__default', {
|
||||||
|
type: 'cachedNamespace',
|
||||||
|
extractionNamespace: {
|
||||||
|
type: 'jdbc',
|
||||||
|
namespace: 'some_lookup',
|
||||||
|
connectorConfig: {
|
||||||
|
createTables: true,
|
||||||
|
connectURI: 'jdbc:mysql://localhost:3306/druid',
|
||||||
|
user: 'druid',
|
||||||
|
password: 'diurd',
|
||||||
|
},
|
||||||
|
table: 'some_lookup_table',
|
||||||
|
keyColumn: undefined,
|
||||||
|
valueColumn: 'the_new_dim_value',
|
||||||
|
tsColumn: 'timestamp_column',
|
||||||
|
pollPeriod: 600000,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('No keyColumn', () => {
|
||||||
|
expect(
|
||||||
|
isDisabled('lookup', 'v1', '__default', {
|
||||||
|
type: 'cachedNamespace',
|
||||||
|
extractionNamespace: {
|
||||||
|
type: 'jdbc',
|
||||||
|
namespace: 'some_lookup',
|
||||||
|
connectorConfig: {
|
||||||
|
createTables: true,
|
||||||
|
connectURI: 'jdbc:mysql://localhost:3306/druid',
|
||||||
|
user: 'druid',
|
||||||
|
password: 'diurd',
|
||||||
|
},
|
||||||
|
table: 'some_lookup_table',
|
||||||
|
keyColumn: 'the_old_dim_value',
|
||||||
|
valueColumn: undefined,
|
||||||
|
tsColumn: 'timestamp_column',
|
||||||
|
pollPeriod: 600000,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Type Map Should be enabled', () => {
|
||||||
|
it('Has type and has Map', () => {
|
||||||
|
expect(isDisabled('lookup', 'v1', '__default', { type: 'map', map: { a: 'b' } })).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Type cachedNamespace Should be enabled', () => {
|
||||||
|
describe('ExtractionNamespace type URI', () => {
|
||||||
|
it('Format csv with columns', () => {
|
||||||
|
expect(
|
||||||
|
isDisabled('lookup', 'v1', '__default', {
|
||||||
|
type: 'cachedNamespace',
|
||||||
|
extractionNamespace: {
|
||||||
|
type: 'uri',
|
||||||
|
uriPrefix: 's3://bucket/some/key/prefix/',
|
||||||
|
fileRegex: 'renames-[0-9]*\\.gz',
|
||||||
|
namespaceParseSpec: {
|
||||||
|
format: 'csv',
|
||||||
|
columns: ['key', 'value'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Format csv with skipHeaderRows', () => {
|
||||||
|
expect(
|
||||||
|
isDisabled('lookup', 'v1', '__default', {
|
||||||
|
type: 'cachedNamespace',
|
||||||
|
extractionNamespace: {
|
||||||
|
type: 'uri',
|
||||||
|
uriPrefix: 's3://bucket/some/key/prefix/',
|
||||||
|
fileRegex: 'renames-[0-9]*\\.gz',
|
||||||
|
namespaceParseSpec: {
|
||||||
|
format: 'csv',
|
||||||
|
skipHeaderRows: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Format tsv, only columns', () => {
|
||||||
|
expect(
|
||||||
|
isDisabled('lookup', 'v1', '__default', {
|
||||||
|
type: 'cachedNamespace',
|
||||||
|
extractionNamespace: {
|
||||||
|
type: 'uri',
|
||||||
|
uriPrefix: 's3://bucket/some/key/prefix/',
|
||||||
|
fileRegex: 'renames-[0-9]*\\.gz',
|
||||||
|
namespaceParseSpec: {
|
||||||
|
format: 'tsv',
|
||||||
|
columns: ['key', 'value'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Format tsv, keyFieldName and valueFieldName', () => {
|
||||||
|
expect(
|
||||||
|
isDisabled('lookup', 'v1', '__default', {
|
||||||
|
type: 'cachedNamespace',
|
||||||
|
extractionNamespace: {
|
||||||
|
type: 'uri',
|
||||||
|
uriPrefix: 's3://bucket/some/key/prefix/',
|
||||||
|
fileRegex: 'renames-[0-9]*\\.gz',
|
||||||
|
namespaceParseSpec: {
|
||||||
|
format: 'customJson',
|
||||||
|
valueFieldName: 'value',
|
||||||
|
keyFieldName: 'value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ExtractionNamespace type JDBC', () => {
|
||||||
|
it('No namespace', () => {
|
||||||
|
expect(
|
||||||
|
isDisabled('lookup', 'v1', '__default', {
|
||||||
|
type: 'cachedNamespace',
|
||||||
|
extractionNamespace: {
|
||||||
|
type: 'jdbc',
|
||||||
|
namespace: 'lookup',
|
||||||
|
connectorConfig: {
|
||||||
|
createTables: true,
|
||||||
|
connectURI: 'jdbc:mysql://localhost:3306/druid',
|
||||||
|
user: 'druid',
|
||||||
|
password: 'diurd',
|
||||||
|
},
|
||||||
|
table: 'some_lookup_table',
|
||||||
|
keyColumn: 'the_old_dim_value',
|
||||||
|
valueColumn: 'the_new_dim_value',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -26,24 +26,120 @@ import {
|
||||||
Intent,
|
Intent,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import AceEditor from 'react-ace';
|
|
||||||
|
|
||||||
import { validJson } from '../../utils';
|
import { AutoForm, Field } from '../../components';
|
||||||
|
|
||||||
import './lookup-edit-dialog.scss';
|
import './lookup-edit-dialog.scss';
|
||||||
|
|
||||||
|
export interface ExtractionNamespaceSpec {
|
||||||
|
type?: string;
|
||||||
|
uri?: string;
|
||||||
|
uriPrefix?: string;
|
||||||
|
fileRegex?: string;
|
||||||
|
namespaceParseSpec?: NamespaceParseSpec;
|
||||||
|
namespace?: string;
|
||||||
|
connectorConfig?: {
|
||||||
|
createTables: boolean;
|
||||||
|
connectURI: string;
|
||||||
|
user: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
table?: string;
|
||||||
|
keyColumn?: string;
|
||||||
|
valueColumn?: string;
|
||||||
|
filter?: any;
|
||||||
|
tsColumn?: string;
|
||||||
|
pollPeriod?: number | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NamespaceParseSpec {
|
||||||
|
format: string;
|
||||||
|
columns?: string[];
|
||||||
|
keyColumn?: string;
|
||||||
|
valueColumn?: string;
|
||||||
|
hasHeaderRow?: boolean;
|
||||||
|
skipHeaderRows?: number;
|
||||||
|
keyFieldName?: string;
|
||||||
|
valueFieldName?: string;
|
||||||
|
delimiter?: string;
|
||||||
|
listDelimiter?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LookupSpec {
|
||||||
|
type?: string;
|
||||||
|
map?: {};
|
||||||
|
extractionNamespace?: ExtractionNamespaceSpec;
|
||||||
|
firstCacheTimeout?: number;
|
||||||
|
injective?: boolean;
|
||||||
|
}
|
||||||
export interface LookupEditDialogProps {
|
export interface LookupEditDialogProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSubmit: () => void;
|
onSubmit: (updateLookupVersion: boolean) => void;
|
||||||
onChange: (field: string, value: string) => void;
|
onChange: (field: string, value: string | LookupSpec) => void;
|
||||||
lookupName: string;
|
lookupName: string;
|
||||||
lookupTier: string;
|
lookupTier: string;
|
||||||
lookupVersion: string;
|
lookupVersion: string;
|
||||||
lookupSpec: string;
|
lookupSpec: LookupSpec;
|
||||||
isEdit: boolean;
|
isEdit: boolean;
|
||||||
allLookupTiers: string[];
|
allLookupTiers: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isDisabled(
|
||||||
|
lookupName?: string,
|
||||||
|
lookupVersion?: string,
|
||||||
|
lookupTier?: string,
|
||||||
|
lookupSpec?: LookupSpec,
|
||||||
|
) {
|
||||||
|
let disableSubmit =
|
||||||
|
!lookupName ||
|
||||||
|
!lookupVersion ||
|
||||||
|
!lookupTier ||
|
||||||
|
!lookupSpec ||
|
||||||
|
!lookupName ||
|
||||||
|
lookupName === '' ||
|
||||||
|
lookupVersion === '' ||
|
||||||
|
lookupTier === '' ||
|
||||||
|
lookupSpec.type === '' ||
|
||||||
|
lookupSpec.type === undefined ||
|
||||||
|
(lookupSpec.type === 'map' && lookupSpec.map === undefined) ||
|
||||||
|
(lookupSpec.type === 'cachedNamespace' && lookupSpec.extractionNamespace === undefined);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!disableSubmit &&
|
||||||
|
lookupSpec &&
|
||||||
|
lookupSpec.type === 'cachedNamespace' &&
|
||||||
|
lookupSpec.extractionNamespace
|
||||||
|
) {
|
||||||
|
switch (lookupSpec.extractionNamespace.type) {
|
||||||
|
case 'uri':
|
||||||
|
const namespaceParseSpec = lookupSpec.extractionNamespace.namespaceParseSpec;
|
||||||
|
disableSubmit = !namespaceParseSpec;
|
||||||
|
if (!namespaceParseSpec) break;
|
||||||
|
switch (namespaceParseSpec.format) {
|
||||||
|
case 'csv':
|
||||||
|
disableSubmit = !namespaceParseSpec.columns && !namespaceParseSpec.skipHeaderRows;
|
||||||
|
break;
|
||||||
|
case 'tsv':
|
||||||
|
disableSubmit = !namespaceParseSpec.columns;
|
||||||
|
break;
|
||||||
|
case 'customJson':
|
||||||
|
disableSubmit = !namespaceParseSpec.keyFieldName || !namespaceParseSpec.valueFieldName;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'jdbc':
|
||||||
|
const extractionNamespace = lookupSpec.extractionNamespace;
|
||||||
|
disableSubmit =
|
||||||
|
!extractionNamespace.namespace ||
|
||||||
|
!extractionNamespace.connectorConfig ||
|
||||||
|
!extractionNamespace.table ||
|
||||||
|
!extractionNamespace.keyColumn ||
|
||||||
|
!extractionNamespace.valueColumn;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return disableSubmit;
|
||||||
|
}
|
||||||
export const LookupEditDialog = React.memo(function LookupEditDialog(props: LookupEditDialogProps) {
|
export const LookupEditDialog = React.memo(function LookupEditDialog(props: LookupEditDialogProps) {
|
||||||
const {
|
const {
|
||||||
onClose,
|
onClose,
|
||||||
|
@ -57,6 +153,8 @@ export const LookupEditDialog = React.memo(function LookupEditDialog(props: Look
|
||||||
allLookupTiers,
|
allLookupTiers,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
let updateVersionOnSubmit = true;
|
||||||
|
|
||||||
function addISOVersion() {
|
function addISOVersion() {
|
||||||
const currentDate = new Date();
|
const currentDate = new Date();
|
||||||
const ISOString = currentDate.toISOString();
|
const ISOString = currentDate.toISOString();
|
||||||
|
@ -66,17 +164,20 @@ export const LookupEditDialog = React.memo(function LookupEditDialog(props: Look
|
||||||
function renderTierInput() {
|
function renderTierInput() {
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
return (
|
return (
|
||||||
<FormGroup className="lookup-label" label="Tier: ">
|
<FormGroup className="lookup-label" label="Tier">
|
||||||
<InputGroup
|
<InputGroup
|
||||||
value={lookupTier}
|
value={lookupTier}
|
||||||
onChange={(e: any) => onChange('lookupEditTier', e.target.value)}
|
onChange={(e: any) => {
|
||||||
|
updateVersionOnSubmit = false;
|
||||||
|
onChange('lookupEditTier', e.target.value);
|
||||||
|
}}
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<FormGroup className="lookup-label" label="Tier:">
|
<FormGroup className="lookup-label" label="Tier">
|
||||||
<HTMLSelect
|
<HTMLSelect
|
||||||
disabled={isEdit}
|
disabled={isEdit}
|
||||||
value={lookupTier}
|
value={lookupTier}
|
||||||
|
@ -93,8 +194,383 @@ export const LookupEditDialog = React.memo(function LookupEditDialog(props: Look
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const disableSubmit =
|
const fields = [
|
||||||
lookupName === '' || lookupVersion === '' || lookupTier === '' || !validJson(lookupSpec);
|
{
|
||||||
|
name: 'type',
|
||||||
|
type: 'string',
|
||||||
|
suggestions: ['map', 'cachedNamespace'],
|
||||||
|
adjustment: (model: LookupSpec) => {
|
||||||
|
if (model.type === 'map' && model.extractionNamespace && model.extractionNamespace.type) {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
model.extractionNamespace = { type: 'uri', namespaceParseSpec: { format: 'csv' } };
|
||||||
|
return model;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'map',
|
||||||
|
type: 'json',
|
||||||
|
defined: (model: LookupSpec) => {
|
||||||
|
return model.type === 'map';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.type',
|
||||||
|
type: 'string',
|
||||||
|
label: 'Globally cached lookup type',
|
||||||
|
placeholder: 'uri',
|
||||||
|
suggestions: ['uri', 'jdbc'],
|
||||||
|
defined: (model: LookupSpec) => model.type === 'cachedNamespace',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.uriPrefix',
|
||||||
|
type: 'string',
|
||||||
|
label: 'URI prefix',
|
||||||
|
info:
|
||||||
|
'A URI which specifies a directory (or other searchable resource) in which to search for files',
|
||||||
|
placeholder: 's3://bucket/some/key/prefix/',
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'uri',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.fileRegex',
|
||||||
|
type: 'string',
|
||||||
|
label: 'File regex',
|
||||||
|
placeholder: '(optional)',
|
||||||
|
info:
|
||||||
|
'Optional regex for matching the file name under uriPrefix. Only used if uriPrefix is used',
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'uri',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.namespaceParseSpec.format',
|
||||||
|
type: 'string',
|
||||||
|
label: 'Format',
|
||||||
|
defaultValue: 'csv',
|
||||||
|
suggestions: ['csv', 'tsv', 'customJson', 'simpleJson'],
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'uri',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.namespaceParseSpec.columns',
|
||||||
|
type: 'string-array',
|
||||||
|
label: 'Columns',
|
||||||
|
placeholder: `["key", "value"]`,
|
||||||
|
info: 'The list of columns in the csv file',
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'uri' &&
|
||||||
|
model.extractionNamespace.namespaceParseSpec &&
|
||||||
|
(model.extractionNamespace.namespaceParseSpec.format === 'csv' ||
|
||||||
|
model.extractionNamespace.namespaceParseSpec.format === 'tsv'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.namespaceParseSpec.keyColumn',
|
||||||
|
type: 'string',
|
||||||
|
label: 'Key column',
|
||||||
|
placeholder: 'Key',
|
||||||
|
info: 'The name of the column containing the key',
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'uri' &&
|
||||||
|
model.extractionNamespace.namespaceParseSpec &&
|
||||||
|
(model.extractionNamespace.namespaceParseSpec.format === 'csv' ||
|
||||||
|
model.extractionNamespace.namespaceParseSpec.format === 'tsv'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.namespaceParseSpec.valueColumn',
|
||||||
|
type: 'string',
|
||||||
|
label: 'Value column',
|
||||||
|
placeholder: 'Value',
|
||||||
|
info: 'The name of the column containing the value',
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'uri' &&
|
||||||
|
model.extractionNamespace.namespaceParseSpec &&
|
||||||
|
(model.extractionNamespace.namespaceParseSpec.format === 'csv' ||
|
||||||
|
model.extractionNamespace.namespaceParseSpec.format === 'tsv'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.namespaceParseSpec.hasHeaderRow',
|
||||||
|
type: 'boolean',
|
||||||
|
label: 'Has header row',
|
||||||
|
defaultValue: false,
|
||||||
|
info: `A flag to indicate that column information can be extracted from the input files' header row`,
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'uri' &&
|
||||||
|
model.extractionNamespace.namespaceParseSpec &&
|
||||||
|
(model.extractionNamespace.namespaceParseSpec.format === 'csv' ||
|
||||||
|
model.extractionNamespace.namespaceParseSpec.format === 'tsv'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.namespaceParseSpec.skipHeaderRows',
|
||||||
|
type: 'number',
|
||||||
|
label: 'Skip header rows',
|
||||||
|
placeholder: '(optional)',
|
||||||
|
info: `Number of header rows to be skipped. The default number of header rows to be skipped is 0.`,
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'uri' &&
|
||||||
|
model.extractionNamespace.namespaceParseSpec &&
|
||||||
|
(model.extractionNamespace.namespaceParseSpec.format === 'csv' ||
|
||||||
|
model.extractionNamespace.namespaceParseSpec.format === 'tsv'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.namespaceParseSpec.delimiter',
|
||||||
|
type: 'string',
|
||||||
|
label: 'Delimiter',
|
||||||
|
placeholder: `(optional)`,
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'uri' &&
|
||||||
|
model.extractionNamespace.namespaceParseSpec &&
|
||||||
|
model.extractionNamespace.namespaceParseSpec.format === 'tsv',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.namespaceParseSpec.listDelimiter',
|
||||||
|
type: 'string',
|
||||||
|
label: 'List delimiter',
|
||||||
|
placeholder: `(optional)`,
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'uri' &&
|
||||||
|
model.extractionNamespace.namespaceParseSpec &&
|
||||||
|
model.extractionNamespace.namespaceParseSpec.format === 'tsv',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.namespaceParseSpec.keyFieldName',
|
||||||
|
type: 'string',
|
||||||
|
label: 'Key field name',
|
||||||
|
placeholder: `key`,
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'uri' &&
|
||||||
|
model.extractionNamespace.namespaceParseSpec &&
|
||||||
|
model.extractionNamespace.namespaceParseSpec.format === 'customJson',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.namespaceParseSpec.valueFieldName',
|
||||||
|
type: 'string',
|
||||||
|
label: 'Value field name',
|
||||||
|
placeholder: `value`,
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'uri' &&
|
||||||
|
model.extractionNamespace.namespaceParseSpec &&
|
||||||
|
model.extractionNamespace.namespaceParseSpec.format === 'customJson',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.namespace',
|
||||||
|
type: 'string',
|
||||||
|
label: 'Namespace',
|
||||||
|
placeholder: 'some_lookup',
|
||||||
|
info: (
|
||||||
|
<>
|
||||||
|
<p>The namespace value in the SQL query:</p>
|
||||||
|
<p>
|
||||||
|
SELECT keyColumn, valueColumn, tsColumn? FROM <strong>namespace</strong>.table WHERE
|
||||||
|
filter
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'jdbc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.connectorConfig.createTables',
|
||||||
|
type: 'boolean',
|
||||||
|
label: 'CreateTables',
|
||||||
|
info: 'Defines the connectURI value on the The connector config to used',
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'jdbc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.connectorConfig.connectURI',
|
||||||
|
type: 'string',
|
||||||
|
label: 'Connect URI',
|
||||||
|
info: 'Defines the connectURI value on the The connector config to used',
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'jdbc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.connectorConfig.user',
|
||||||
|
type: 'string',
|
||||||
|
label: 'User',
|
||||||
|
info: 'Defines the user to be used by the connector config',
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'jdbc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.connectorConfig.password',
|
||||||
|
type: 'string',
|
||||||
|
label: 'Password',
|
||||||
|
info: 'Defines the password to be used by the connector config',
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'jdbc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.table',
|
||||||
|
type: 'string',
|
||||||
|
label: 'Table',
|
||||||
|
placeholder: 'some_lookup_table',
|
||||||
|
info: (
|
||||||
|
<>
|
||||||
|
<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>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'jdbc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.keyColumn',
|
||||||
|
type: 'string',
|
||||||
|
label: 'Key column',
|
||||||
|
placeholder: 'my_key_value',
|
||||||
|
info: (
|
||||||
|
<>
|
||||||
|
<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>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'jdbc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.valueColumn',
|
||||||
|
type: 'string',
|
||||||
|
label: 'Value column',
|
||||||
|
placeholder: 'my_column_value',
|
||||||
|
info: (
|
||||||
|
<>
|
||||||
|
<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>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'jdbc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.filter',
|
||||||
|
type: 'string',
|
||||||
|
label: 'Filter',
|
||||||
|
placeholder: '(optional)',
|
||||||
|
info: (
|
||||||
|
<>
|
||||||
|
<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>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'jdbc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.tsColumn',
|
||||||
|
type: 'string',
|
||||||
|
label: 'TsColumn',
|
||||||
|
placeholder: '(optional)',
|
||||||
|
info: (
|
||||||
|
<>
|
||||||
|
<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>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'jdbc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.pollPeriod',
|
||||||
|
type: 'string',
|
||||||
|
label: 'Poll period',
|
||||||
|
placeholder: '(optional)',
|
||||||
|
info: `Period between polling for updates`,
|
||||||
|
defined: (model: LookupSpec) =>
|
||||||
|
model.type === 'cachedNamespace' &&
|
||||||
|
!!model.extractionNamespace &&
|
||||||
|
model.extractionNamespace.type === 'uri',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'firstCacheTimeout',
|
||||||
|
type: 'number',
|
||||||
|
label: 'First cache timeout',
|
||||||
|
placeholder: '(optional)',
|
||||||
|
info: `How long to wait (in ms) for the first run of the cache to populate. 0 indicates to not wait`,
|
||||||
|
defined: (model: LookupSpec) => model.type === 'cachedNamespace',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'injective',
|
||||||
|
type: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
|
info: `If the underlying map is injective (keys and values are unique) then optimizations can occur internally by setting this to true`,
|
||||||
|
defined: (model: LookupSpec) => model.type === 'cachedNamespace',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
|
@ -103,7 +579,7 @@ export const LookupEditDialog = React.memo(function LookupEditDialog(props: Look
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={isEdit ? 'Edit lookup' : 'Add lookup'}
|
title={isEdit ? 'Edit lookup' : 'Add lookup'}
|
||||||
>
|
>
|
||||||
<FormGroup className="lookup-label" label="Name: ">
|
<FormGroup className="lookup-label" label="Name">
|
||||||
<InputGroup
|
<InputGroup
|
||||||
value={lookupName}
|
value={lookupName}
|
||||||
onChange={(e: any) => onChange('lookupEditName', e.target.value)}
|
onChange={(e: any) => onChange('lookupEditName', e.target.value)}
|
||||||
|
@ -111,10 +587,8 @@ export const LookupEditDialog = React.memo(function LookupEditDialog(props: Look
|
||||||
placeholder="Enter the lookup name"
|
placeholder="Enter the lookup name"
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
{renderTierInput()}
|
{renderTierInput()}
|
||||||
|
<FormGroup className="lookup-label" label="Version">
|
||||||
<FormGroup className="lookup-label" label="Version:">
|
|
||||||
<InputGroup
|
<InputGroup
|
||||||
value={lookupVersion}
|
value={lookupVersion}
|
||||||
onChange={(e: any) => onChange('lookupEditVersion', e.target.value)}
|
onChange={(e: any) => onChange('lookupEditVersion', e.target.value)}
|
||||||
|
@ -124,35 +598,23 @@ export const LookupEditDialog = React.memo(function LookupEditDialog(props: Look
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
<AutoForm
|
||||||
<FormGroup className="lookup-label" label="Spec:" />
|
fields={fields as Field<LookupSpec>[]}
|
||||||
|
model={lookupSpec}
|
||||||
<AceEditor
|
onChange={m => {
|
||||||
className="lookup-edit-dialog-textarea"
|
onChange('lookupEditSpec', m);
|
||||||
mode="hjson"
|
|
||||||
theme="solarized_dark"
|
|
||||||
onChange={(e: any) => onChange('lookupEditSpec', e)}
|
|
||||||
fontSize={12}
|
|
||||||
height="40vh"
|
|
||||||
width="auto"
|
|
||||||
showPrintMargin={false}
|
|
||||||
showGutter={false}
|
|
||||||
value={lookupSpec}
|
|
||||||
editorProps={{ $blockScrolling: Infinity }}
|
|
||||||
setOptions={{
|
|
||||||
tabSize: 2,
|
|
||||||
}}
|
}}
|
||||||
style={{}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={Classes.DIALOG_FOOTER}>
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
<Button text="Close" onClick={onClose} />
|
<Button text="Close" onClick={onClose} />
|
||||||
<Button
|
<Button
|
||||||
text="Submit"
|
text="Submit"
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
onClick={() => onSubmit()}
|
onClick={() => {
|
||||||
disabled={disableSubmit}
|
onSubmit(updateVersionOnSubmit && isEdit);
|
||||||
|
}}
|
||||||
|
disabled={isDisabled(lookupName, lookupVersion, lookupTier, lookupSpec)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -32,6 +32,7 @@ import {
|
||||||
ViewControlBar,
|
ViewControlBar,
|
||||||
} from '../../components';
|
} from '../../components';
|
||||||
import { AsyncActionDialog, LookupEditDialog } from '../../dialogs/';
|
import { AsyncActionDialog, LookupEditDialog } from '../../dialogs/';
|
||||||
|
import { LookupSpec } from '../../dialogs/lookup-edit-dialog/lookup-edit-dialog';
|
||||||
import { LookupTableActionDialog } from '../../dialogs/lookup-table-action-dialog/lookup-table-action-dialog';
|
import { LookupTableActionDialog } from '../../dialogs/lookup-table-action-dialog/lookup-table-action-dialog';
|
||||||
import { AppToaster } from '../../singletons/toaster';
|
import { AppToaster } from '../../singletons/toaster';
|
||||||
import { getDruidErrorMessage, LocalStorageKeys, QueryManager } from '../../utils';
|
import { getDruidErrorMessage, LocalStorageKeys, QueryManager } from '../../utils';
|
||||||
|
@ -55,7 +56,7 @@ export interface LookupsViewState {
|
||||||
lookupEditName: string;
|
lookupEditName: string;
|
||||||
lookupEditTier: string;
|
lookupEditTier: string;
|
||||||
lookupEditVersion: string;
|
lookupEditVersion: string;
|
||||||
lookupEditSpec: string;
|
lookupEditSpec: LookupSpec;
|
||||||
isEdit: boolean;
|
isEdit: boolean;
|
||||||
allLookupTiers: string[];
|
allLookupTiers: string[];
|
||||||
|
|
||||||
|
@ -81,7 +82,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
||||||
lookupEditTier: '',
|
lookupEditTier: '',
|
||||||
lookupEditName: '',
|
lookupEditName: '',
|
||||||
lookupEditVersion: '',
|
lookupEditVersion: '',
|
||||||
lookupEditSpec: '',
|
lookupEditSpec: { type: '' },
|
||||||
isEdit: false,
|
isEdit: false,
|
||||||
allLookupTiers: [],
|
allLookupTiers: [],
|
||||||
actions: [],
|
actions: [],
|
||||||
|
@ -162,7 +163,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
||||||
lookupEditName: '',
|
lookupEditName: '',
|
||||||
lookupEditTier: prevState.allLookupTiers[0],
|
lookupEditTier: prevState.allLookupTiers[0],
|
||||||
lookupEditDialogOpen: true,
|
lookupEditDialogOpen: true,
|
||||||
lookupEditSpec: '',
|
lookupEditSpec: { type: '' },
|
||||||
lookupEditVersion: new Date().toISOString(),
|
lookupEditVersion: new Date().toISOString(),
|
||||||
isEdit: false,
|
isEdit: false,
|
||||||
}));
|
}));
|
||||||
|
@ -171,20 +172,20 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
||||||
lookupEditName: id,
|
lookupEditName: id,
|
||||||
lookupEditTier: tier,
|
lookupEditTier: tier,
|
||||||
lookupEditDialogOpen: true,
|
lookupEditDialogOpen: true,
|
||||||
lookupEditSpec: JSON.stringify(target.spec, null, 2),
|
lookupEditSpec: target.spec,
|
||||||
lookupEditVersion: target.version,
|
lookupEditVersion: target.version,
|
||||||
isEdit: true,
|
isEdit: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleChangeLookup = (field: string, value: string) => {
|
private handleChangeLookup = (field: string, value: string | LookupSpec) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
[field]: value,
|
[field]: value,
|
||||||
} as any);
|
} as any);
|
||||||
};
|
};
|
||||||
|
|
||||||
private async submitLookupEdit() {
|
private async submitLookupEdit(updatelookupEditVersion: boolean) {
|
||||||
const {
|
const {
|
||||||
lookupEditTier,
|
lookupEditTier,
|
||||||
lookupEditName,
|
lookupEditName,
|
||||||
|
@ -192,20 +193,21 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
||||||
lookupEditVersion,
|
lookupEditVersion,
|
||||||
isEdit,
|
isEdit,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
const version = updatelookupEditVersion ? new Date().toISOString() : lookupEditVersion;
|
||||||
let endpoint = '/druid/coordinator/v1/lookups/config';
|
let endpoint = '/druid/coordinator/v1/lookups/config';
|
||||||
const specJson: any = JSON.parse(lookupEditSpec);
|
const specJson: any = lookupEditSpec;
|
||||||
let dataJson: any;
|
let dataJson: any;
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
endpoint = `${endpoint}/${lookupEditTier}/${lookupEditName}`;
|
endpoint = `${endpoint}/${lookupEditTier}/${lookupEditName}`;
|
||||||
dataJson = {
|
dataJson = {
|
||||||
version: lookupEditVersion,
|
version: version,
|
||||||
lookupExtractorFactory: specJson,
|
lookupExtractorFactory: specJson,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
dataJson = {
|
dataJson = {
|
||||||
[lookupEditTier]: {
|
[lookupEditTier]: {
|
||||||
[lookupEditName]: {
|
[lookupEditName]: {
|
||||||
version: lookupEditVersion,
|
version: version,
|
||||||
lookupExtractorFactory: specJson,
|
lookupExtractorFactory: specJson,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -374,7 +376,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
||||||
return (
|
return (
|
||||||
<LookupEditDialog
|
<LookupEditDialog
|
||||||
onClose={() => this.setState({ lookupEditDialogOpen: false })}
|
onClose={() => this.setState({ lookupEditDialogOpen: false })}
|
||||||
onSubmit={() => this.submitLookupEdit()}
|
onSubmit={updateLookupVersion => this.submitLookupEdit(updateLookupVersion)}
|
||||||
onChange={this.handleChangeLookup}
|
onChange={this.handleChangeLookup}
|
||||||
lookupSpec={lookupEditSpec}
|
lookupSpec={lookupEditSpec}
|
||||||
lookupName={lookupEditName}
|
lookupName={lookupEditName}
|
||||||
|
|
Loading…
Reference in New Issue