mirror of
https://github.com/apache/druid.git
synced 2025-02-08 19:14:49 +00:00
Web console: Improve the lookup view UX (#11620)
* polish lookup view UX * update snapshots * add snapshot to git * fixes * update sanpshots * restore column treatment * update snapshot * add gs
This commit is contained in:
parent
a09688862e
commit
e4ec3527a4
@ -23,7 +23,7 @@ const fs = require('fs-extra');
|
|||||||
const readfile = '../docs/querying/sql.md';
|
const readfile = '../docs/querying/sql.md';
|
||||||
const writefile = 'lib/sql-docs.js';
|
const writefile = 'lib/sql-docs.js';
|
||||||
|
|
||||||
const MINIMUM_EXPECTED_NUMBER_OF_FUNCTIONS = 134;
|
const MINIMUM_EXPECTED_NUMBER_OF_FUNCTIONS = 152;
|
||||||
const MINIMUM_EXPECTED_NUMBER_OF_DATA_TYPES = 14;
|
const MINIMUM_EXPECTED_NUMBER_OF_DATA_TYPES = 14;
|
||||||
|
|
||||||
function unwrapMarkdownLinks(str) {
|
function unwrapMarkdownLinks(str) {
|
||||||
@ -41,7 +41,7 @@ const readDoc = async () => {
|
|||||||
const functionDocs = [];
|
const functionDocs = [];
|
||||||
const dataTypeDocs = [];
|
const dataTypeDocs = [];
|
||||||
for (let line of lines) {
|
for (let line of lines) {
|
||||||
const functionMatch = line.match(/^\|`(\w+)\(([^|]*)\)`\|([^|]+)\|(?:([^|]+)\|)?$/);
|
const functionMatch = line.match(/^\|\s*`(\w+)\(([^|]*)\)`\s*\|([^|]+)\|(?:([^|]+)\|)?$/);
|
||||||
if (functionMatch) {
|
if (functionMatch) {
|
||||||
functionDocs.push([
|
functionDocs.push([
|
||||||
functionMatch[1],
|
functionMatch[1],
|
||||||
|
@ -20,7 +20,7 @@ import { Button, ButtonGroup, FormGroup, Intent, NumericInput } from '@blueprint
|
|||||||
import { IconNames } from '@blueprintjs/icons';
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { deepDelete, deepGet, deepSet } from '../../utils';
|
import { deepDelete, deepGet, deepSet, durationSanitizer } from '../../utils';
|
||||||
import { ArrayInput } from '../array-input/array-input';
|
import { ArrayInput } from '../array-input/array-input';
|
||||||
import { FormGroupWithInfo } from '../form-group-with-info/form-group-with-info';
|
import { FormGroupWithInfo } from '../form-group-with-info/form-group-with-info';
|
||||||
import { IntervalInput } from '../interval-input/interval-input';
|
import { IntervalInput } from '../interval-input/interval-input';
|
||||||
@ -281,15 +281,16 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderStringInput(field: Field<T>, sanitize?: (str: string) => string): JSX.Element {
|
private renderStringInput(field: Field<T>, sanitizer?: (str: string) => string): JSX.Element {
|
||||||
const { model, large, onFinalize } = this.props;
|
const { model, large, onFinalize } = this.props;
|
||||||
const { required, defaultValue, modelValue } = AutoForm.computeFieldValues(model, field);
|
const { required, defaultValue, modelValue } = AutoForm.computeFieldValues(model, field);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SuggestibleInput
|
<SuggestibleInput
|
||||||
value={modelValue != null ? modelValue : defaultValue || ''}
|
value={modelValue != null ? modelValue : defaultValue || ''}
|
||||||
|
sanitizer={sanitizer}
|
||||||
|
issueWithValue={field.issueWithValue}
|
||||||
onValueChange={v => {
|
onValueChange={v => {
|
||||||
if (sanitize && typeof v === 'string') v = sanitize(v);
|
|
||||||
this.fieldChange(field, v);
|
this.fieldChange(field, v);
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
@ -397,9 +398,7 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
|
|||||||
case 'string':
|
case 'string':
|
||||||
return this.renderStringInput(field);
|
return this.renderStringInput(field);
|
||||||
case 'duration':
|
case 'duration':
|
||||||
return this.renderStringInput(field, (str: string) =>
|
return this.renderStringInput(field, durationSanitizer);
|
||||||
str.toUpperCase().replace(/[^0-9PYMDTHS.,]/g, ''),
|
|
||||||
);
|
|
||||||
case 'boolean':
|
case 'boolean':
|
||||||
return this.renderBooleanInput(field);
|
return this.renderBooleanInput(field);
|
||||||
case 'string-array':
|
case 'string-array':
|
||||||
|
@ -13,7 +13,7 @@ exports[`form group with info matches snapshot 1`] = `
|
|||||||
class="bp3-text-muted"
|
class="bp3-text-muted"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bp3-popover2-target"
|
class="info-popover bp3-popover2-target"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bp3-icon bp3-icon-info-sign"
|
class="bp3-icon bp3-icon-info-sign"
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
.bp3-form-content {
|
.bp3-form-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
& > .bp3-popover2-target {
|
& > .info-popover {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
|
@ -36,7 +36,7 @@ export const FormGroupWithInfo = React.memo(function FormGroupWithInfo(
|
|||||||
const { label, info, inlineInfo, children } = props;
|
const { label, info, inlineInfo, children } = props;
|
||||||
|
|
||||||
const popover = (
|
const popover = (
|
||||||
<Popover2 content={info} position="left-bottom">
|
<Popover2 className="info-popover" content={info} position="left-bottom">
|
||||||
<Icon icon={IconNames.INFO_SIGN} iconSize={14} />
|
<Icon icon={IconNames.INFO_SIGN} iconSize={14} />
|
||||||
</Popover2>
|
</Popover2>
|
||||||
);
|
);
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`FormattedInputGroup matches snapshot on undefined value 1`] = `
|
|
||||||
<div
|
|
||||||
class="bp3-input-group formatted-input-group"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
class="bp3-input"
|
|
||||||
type="text"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`FormattedInputGroup matches snapshot with escaped value 1`] = `
|
|
||||||
<div
|
|
||||||
class="bp3-input-group formatted-input-group"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
class="bp3-input"
|
|
||||||
type="text"
|
|
||||||
value="Here are some chars \\\\t\\\\r\\\\n lol"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`;
|
|
@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 { InputGroup, InputGroupProps2 } from '@blueprintjs/core';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import React, { useState } from 'react';
|
|
||||||
|
|
||||||
import { Formatter } from '../../utils';
|
|
||||||
|
|
||||||
export interface FormattedInputGroupProps extends InputGroupProps2 {
|
|
||||||
formatter: Formatter<any>;
|
|
||||||
onValueChange: (newValue: undefined | string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FormattedInputGroup = React.memo(function FormattedInputGroup(
|
|
||||||
props: FormattedInputGroupProps,
|
|
||||||
) {
|
|
||||||
const { className, formatter, value, defaultValue, onValueChange, onBlur, ...rest } = props;
|
|
||||||
|
|
||||||
const [intermediateValue, setIntermediateValue] = useState<string | undefined>();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputGroup
|
|
||||||
className={classNames('formatted-input-group', className)}
|
|
||||||
value={
|
|
||||||
typeof intermediateValue !== 'undefined'
|
|
||||||
? intermediateValue
|
|
||||||
: typeof value !== 'undefined'
|
|
||||||
? formatter.stringify(value)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
defaultValue={
|
|
||||||
typeof defaultValue !== 'undefined' ? formatter.stringify(defaultValue) : undefined
|
|
||||||
}
|
|
||||||
onChange={e => {
|
|
||||||
const rawValue = e.target.value;
|
|
||||||
setIntermediateValue(rawValue);
|
|
||||||
|
|
||||||
let parsedValue: string | undefined;
|
|
||||||
try {
|
|
||||||
parsedValue = formatter.parse(rawValue);
|
|
||||||
} catch {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onValueChange(parsedValue);
|
|
||||||
}}
|
|
||||||
onBlur={e => {
|
|
||||||
setIntermediateValue(undefined);
|
|
||||||
onBlur?.(e);
|
|
||||||
}}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
@ -0,0 +1,33 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`FormattedInput matches snapshot on undefined value 1`] = `
|
||||||
|
<div
|
||||||
|
class="formatted-input"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bp3-input-group"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="bp3-input"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`FormattedInput matches snapshot with escaped value 1`] = `
|
||||||
|
<div
|
||||||
|
class="formatted-input"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bp3-input-group"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="bp3-input"
|
||||||
|
type="text"
|
||||||
|
value="Here are some chars \\\\t\\\\r\\\\n lol"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.formatted-input {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
& > .bp3-popover2-target {
|
||||||
|
position: absolute;
|
||||||
|
width: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
@ -21,12 +21,12 @@ import React from 'react';
|
|||||||
|
|
||||||
import { JSON_STRING_FORMATTER } from '../../utils';
|
import { JSON_STRING_FORMATTER } from '../../utils';
|
||||||
|
|
||||||
import { FormattedInputGroup } from './formatted-input-group';
|
import { FormattedInput } from './formatted-input';
|
||||||
|
|
||||||
describe('FormattedInputGroup', () => {
|
describe('FormattedInput', () => {
|
||||||
it('matches snapshot on undefined value', () => {
|
it('matches snapshot on undefined value', () => {
|
||||||
const suggestibleInput = (
|
const suggestibleInput = (
|
||||||
<FormattedInputGroup onValueChange={() => {}} formatter={JSON_STRING_FORMATTER} />
|
<FormattedInput onValueChange={() => {}} formatter={JSON_STRING_FORMATTER} />
|
||||||
);
|
);
|
||||||
|
|
||||||
const { container } = render(suggestibleInput);
|
const { container } = render(suggestibleInput);
|
||||||
@ -35,7 +35,7 @@ describe('FormattedInputGroup', () => {
|
|||||||
|
|
||||||
it('matches snapshot with escaped value', () => {
|
it('matches snapshot with escaped value', () => {
|
||||||
const suggestibleInput = (
|
const suggestibleInput = (
|
||||||
<FormattedInputGroup
|
<FormattedInput
|
||||||
value={`Here are some chars \t\r\n lol`}
|
value={`Here are some chars \t\r\n lol`}
|
||||||
onValueChange={() => {}}
|
onValueChange={() => {}}
|
||||||
formatter={JSON_STRING_FORMATTER}
|
formatter={JSON_STRING_FORMATTER}
|
107
web-console/src/components/formatted-input/formatted-input.tsx
Normal file
107
web-console/src/components/formatted-input/formatted-input.tsx
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* 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 { InputGroup, InputGroupProps2, Intent } from '@blueprintjs/core';
|
||||||
|
import { Tooltip2 } from '@blueprintjs/popover2';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import { Formatter } from '../../utils';
|
||||||
|
|
||||||
|
import './formatted-input.scss';
|
||||||
|
|
||||||
|
export interface FormattedInputProps extends InputGroupProps2 {
|
||||||
|
formatter: Formatter<any>;
|
||||||
|
onValueChange: (newValue: undefined | string) => void;
|
||||||
|
sanitizer?: (rawValue: string) => string;
|
||||||
|
issueWithValue?: (value: any) => string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FormattedInput = React.memo(function FormattedInput(props: FormattedInputProps) {
|
||||||
|
const {
|
||||||
|
className,
|
||||||
|
formatter,
|
||||||
|
sanitizer,
|
||||||
|
issueWithValue,
|
||||||
|
value,
|
||||||
|
defaultValue,
|
||||||
|
onValueChange,
|
||||||
|
onFocus,
|
||||||
|
onBlur,
|
||||||
|
intent,
|
||||||
|
...rest
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const [intermediateValue, setIntermediateValue] = useState<string | undefined>();
|
||||||
|
const [isFocused, setIsFocused] = useState(false);
|
||||||
|
|
||||||
|
const issue: string | undefined = issueWithValue?.(value);
|
||||||
|
const showIssue = Boolean(!isFocused && issue);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames('formatted-input', className)}>
|
||||||
|
<InputGroup
|
||||||
|
value={
|
||||||
|
typeof intermediateValue !== 'undefined'
|
||||||
|
? intermediateValue
|
||||||
|
: typeof value !== 'undefined'
|
||||||
|
? formatter.stringify(value)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
defaultValue={
|
||||||
|
typeof defaultValue !== 'undefined' ? formatter.stringify(defaultValue) : undefined
|
||||||
|
}
|
||||||
|
onChange={e => {
|
||||||
|
let rawValue = e.target.value;
|
||||||
|
if (sanitizer) rawValue = sanitizer(rawValue);
|
||||||
|
setIntermediateValue(rawValue);
|
||||||
|
|
||||||
|
let parsedValue: string | undefined;
|
||||||
|
try {
|
||||||
|
parsedValue = formatter.parse(rawValue);
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onValueChange(parsedValue);
|
||||||
|
}}
|
||||||
|
onFocus={e => {
|
||||||
|
setIsFocused(true);
|
||||||
|
onFocus?.(e);
|
||||||
|
}}
|
||||||
|
onBlur={e => {
|
||||||
|
setIntermediateValue(undefined);
|
||||||
|
setIsFocused(false);
|
||||||
|
onBlur?.(e);
|
||||||
|
}}
|
||||||
|
intent={showIssue ? Intent.DANGER : intent}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
{showIssue && (
|
||||||
|
<Tooltip2
|
||||||
|
isOpen
|
||||||
|
content={showIssue ? issue : undefined}
|
||||||
|
position="right"
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
targetTagName="div"
|
||||||
|
>
|
||||||
|
<div className="target-dummy" />
|
||||||
|
</Tooltip2>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
@ -25,7 +25,7 @@ export * from './center-message/center-message';
|
|||||||
export * from './clearable-input/clearable-input';
|
export * from './clearable-input/clearable-input';
|
||||||
export * from './external-link/external-link';
|
export * from './external-link/external-link';
|
||||||
export * from './form-json-selector/form-json-selector';
|
export * from './form-json-selector/form-json-selector';
|
||||||
export * from './formatted-input-group/formatted-input-group';
|
export * from './formatted-input/formatted-input';
|
||||||
export * from './header-bar/header-bar';
|
export * from './header-bar/header-bar';
|
||||||
export * from './highlight-text/highlight-text';
|
export * from './highlight-text/highlight-text';
|
||||||
export * from './json-collapse/json-collapse';
|
export * from './json-collapse/json-collapse';
|
||||||
|
@ -30,6 +30,7 @@ import {
|
|||||||
import { IconNames } from '@blueprintjs/icons';
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import { durationSanitizer } from '../../utils';
|
||||||
import { Rule, RuleUtil } from '../../utils/load-rule';
|
import { Rule, RuleUtil } from '../../utils/load-rule';
|
||||||
import { SuggestibleInput } from '../suggestible-input/suggestible-input';
|
import { SuggestibleInput } from '../suggestible-input/suggestible-input';
|
||||||
|
|
||||||
@ -175,10 +176,9 @@ export const RuleEditor = React.memo(function RuleEditor(props: RuleEditorProps)
|
|||||||
{RuleUtil.hasPeriod(rule) && (
|
{RuleUtil.hasPeriod(rule) && (
|
||||||
<SuggestibleInput
|
<SuggestibleInput
|
||||||
value={rule.period || ''}
|
value={rule.period || ''}
|
||||||
|
sanitizer={durationSanitizer}
|
||||||
onValueChange={period => {
|
onValueChange={period => {
|
||||||
if (typeof period === 'undefined') return;
|
if (typeof period === 'undefined') return;
|
||||||
// Ensure the period is upper case and does not contain anytihng but the allowed chars
|
|
||||||
period = period.toUpperCase().replace(/[^PYMDTHS0-9]/g, '');
|
|
||||||
onChange(RuleUtil.changePeriod(rule, period));
|
onChange(RuleUtil.changePeriod(rule, period));
|
||||||
}}
|
}}
|
||||||
placeholder={PERIOD_SUGGESTIONS[0]}
|
placeholder={PERIOD_SUGGESTIONS[0]}
|
||||||
|
@ -2,8 +2,11 @@
|
|||||||
|
|
||||||
exports[`SuggestibleInput matches snapshot 1`] = `
|
exports[`SuggestibleInput matches snapshot 1`] = `
|
||||||
<div
|
<div
|
||||||
class="bp3-input-group formatted-input-group suggestible-input"
|
class="formatted-input suggestible-input"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="bp3-input-group"
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
class="bp3-input"
|
class="bp3-input"
|
||||||
style="padding-right: 0px;"
|
style="padding-right: 0px;"
|
||||||
@ -42,13 +45,17 @@ exports[`SuggestibleInput matches snapshot 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SuggestibleInput matches snapshot with escaped value 1`] = `
|
exports[`SuggestibleInput matches snapshot with escaped value 1`] = `
|
||||||
<div
|
<div
|
||||||
class="bp3-input-group formatted-input-group suggestible-input"
|
class="formatted-input suggestible-input"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="bp3-input-group"
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
class="bp3-input"
|
class="bp3-input"
|
||||||
style="padding-right: 0px;"
|
style="padding-right: 0px;"
|
||||||
@ -87,5 +94,6 @@ exports[`SuggestibleInput matches snapshot with escaped value 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@ -23,10 +23,7 @@ import classNames from 'classnames';
|
|||||||
import React, { useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
|
|
||||||
import { JSON_STRING_FORMATTER } from '../../utils';
|
import { JSON_STRING_FORMATTER } from '../../utils';
|
||||||
import {
|
import { FormattedInput, FormattedInputProps } from '../formatted-input/formatted-input';
|
||||||
FormattedInputGroup,
|
|
||||||
FormattedInputGroupProps,
|
|
||||||
} from '../formatted-input-group/formatted-input-group';
|
|
||||||
|
|
||||||
export interface SuggestionGroup {
|
export interface SuggestionGroup {
|
||||||
group: string;
|
group: string;
|
||||||
@ -35,7 +32,7 @@ export interface SuggestionGroup {
|
|||||||
|
|
||||||
export type Suggestion = undefined | string | SuggestionGroup;
|
export type Suggestion = undefined | string | SuggestionGroup;
|
||||||
|
|
||||||
export interface SuggestibleInputProps extends Omit<FormattedInputGroupProps, 'formatter'> {
|
export interface SuggestibleInputProps extends Omit<FormattedInputProps, 'formatter'> {
|
||||||
onFinalize?: () => void;
|
onFinalize?: () => void;
|
||||||
suggestions?: Suggestion[];
|
suggestions?: Suggestion[];
|
||||||
}
|
}
|
||||||
@ -60,7 +57,7 @@ export const SuggestibleInput = React.memo(function SuggestibleInput(props: Sugg
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormattedInputGroup
|
<FormattedInput
|
||||||
className={classNames('suggestible-input', className)}
|
className={classNames('suggestible-input', className)}
|
||||||
formatter={JSON_STRING_FORMATTER}
|
formatter={JSON_STRING_FORMATTER}
|
||||||
value={value}
|
value={value}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
exports[`LookupEditDialog matches snapshot 1`] = `
|
exports[`LookupEditDialog matches snapshot 1`] = `
|
||||||
<Blueprint3.Dialog
|
<Blueprint3.Dialog
|
||||||
|
canEscapeKeyClose={false}
|
||||||
canOutsideClickClose={true}
|
canOutsideClickClose={true}
|
||||||
className="lookup-edit-dialog"
|
className="lookup-edit-dialog"
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
@ -53,7 +54,7 @@ exports[`LookupEditDialog matches snapshot 1`] = `
|
|||||||
<Blueprint3.Button
|
<Blueprint3.Button
|
||||||
minimal={true}
|
minimal={true}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
text="Use ISO as version"
|
text="Set to current ISO time"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
value="test"
|
value="test"
|
||||||
@ -86,7 +87,7 @@ exports[`LookupEditDialog matches snapshot 1`] = `
|
|||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"defined": [Function],
|
"defined": [Function],
|
||||||
"label": "Globally cached lookup type",
|
"label": "Extraction type",
|
||||||
"name": "extractionNamespace.type",
|
"name": "extractionNamespace.type",
|
||||||
"placeholder": "uri",
|
"placeholder": "uri",
|
||||||
"required": true,
|
"required": true,
|
||||||
@ -98,7 +99,27 @@ exports[`LookupEditDialog matches snapshot 1`] = `
|
|||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"defined": [Function],
|
"defined": [Function],
|
||||||
"info": "A URI which specifies a directory (or other searchable resource) in which to search for files",
|
"info": <p>
|
||||||
|
A URI which specifies a directory (or other searchable resource) in which to search for files specified as a
|
||||||
|
<Unknown>
|
||||||
|
file
|
||||||
|
</Unknown>
|
||||||
|
,
|
||||||
|
<Unknown>
|
||||||
|
hdfs
|
||||||
|
</Unknown>
|
||||||
|
,
|
||||||
|
<Unknown>
|
||||||
|
s3
|
||||||
|
</Unknown>
|
||||||
|
, or
|
||||||
|
|
||||||
|
<Unknown>
|
||||||
|
gs
|
||||||
|
</Unknown>
|
||||||
|
path prefix.
|
||||||
|
</p>,
|
||||||
|
"issueWithValue": [Function],
|
||||||
"label": "URI prefix",
|
"label": "URI prefix",
|
||||||
"name": "extractionNamespace.uriPrefix",
|
"name": "extractionNamespace.uriPrefix",
|
||||||
"placeholder": "s3://bucket/some/key/prefix/",
|
"placeholder": "s3://bucket/some/key/prefix/",
|
||||||
@ -109,12 +130,30 @@ exports[`LookupEditDialog matches snapshot 1`] = `
|
|||||||
"defined": [Function],
|
"defined": [Function],
|
||||||
"info": <React.Fragment>
|
"info": <React.Fragment>
|
||||||
<p>
|
<p>
|
||||||
URI for the file of interest, specified as a file, hdfs, or s3 path
|
URI for the file of interest, specified as a
|
||||||
|
<Unknown>
|
||||||
|
file
|
||||||
|
</Unknown>
|
||||||
|
,
|
||||||
|
<Unknown>
|
||||||
|
hdfs
|
||||||
|
</Unknown>
|
||||||
|
,
|
||||||
|
|
||||||
|
<Unknown>
|
||||||
|
s3
|
||||||
|
</Unknown>
|
||||||
|
, or
|
||||||
|
<Unknown>
|
||||||
|
gs
|
||||||
|
</Unknown>
|
||||||
|
path
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The URI prefix option is strictly better than URI and should be used instead
|
The URI prefix option is strictly better than URI and should be used instead
|
||||||
</p>
|
</p>
|
||||||
</React.Fragment>,
|
</React.Fragment>,
|
||||||
|
"issueWithValue": [Function],
|
||||||
"label": "URI (deprecated)",
|
"label": "URI (deprecated)",
|
||||||
"name": "extractionNamespace.uri",
|
"name": "extractionNamespace.uri",
|
||||||
"placeholder": "s3://bucket/some/key/prefix/lookups-01.gz",
|
"placeholder": "s3://bucket/some/key/prefix/lookups-01.gz",
|
||||||
@ -154,10 +193,22 @@ exports[`LookupEditDialog matches snapshot 1`] = `
|
|||||||
],
|
],
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"defaultValue": " ",
|
||||||
|
"defined": [Function],
|
||||||
|
"name": "extractionNamespace.namespaceParseSpec.delimiter",
|
||||||
|
"suggestions": Array [
|
||||||
|
" ",
|
||||||
|
";",
|
||||||
|
"|",
|
||||||
|
"#",
|
||||||
|
],
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"defaultValue": 0,
|
"defaultValue": 0,
|
||||||
"defined": [Function],
|
"defined": [Function],
|
||||||
"info": "Number of header rows to be skipped. The default number of header rows to be skipped is 0.",
|
"info": "Number of header rows to be skipped.",
|
||||||
"name": "extractionNamespace.namespaceParseSpec.skipHeaderRows",
|
"name": "extractionNamespace.namespaceParseSpec.skipHeaderRows",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
},
|
},
|
||||||
@ -172,7 +223,7 @@ exports[`LookupEditDialog matches snapshot 1`] = `
|
|||||||
"defined": [Function],
|
"defined": [Function],
|
||||||
"info": "The list of columns in the csv file",
|
"info": "The list of columns in the csv file",
|
||||||
"name": "extractionNamespace.namespaceParseSpec.columns",
|
"name": "extractionNamespace.namespaceParseSpec.columns",
|
||||||
"placeholder": "[\\"key\\", \\"value\\"]",
|
"placeholder": "key, value",
|
||||||
"required": [Function],
|
"required": [Function],
|
||||||
"type": "string-array",
|
"type": "string-array",
|
||||||
},
|
},
|
||||||
@ -190,18 +241,6 @@ exports[`LookupEditDialog matches snapshot 1`] = `
|
|||||||
"placeholder": "(optional - defaults to the second column)",
|
"placeholder": "(optional - defaults to the second column)",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
Object {
|
|
||||||
"defined": [Function],
|
|
||||||
"name": "extractionNamespace.namespaceParseSpec.delimiter",
|
|
||||||
"placeholder": "(optional)",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"defined": [Function],
|
|
||||||
"name": "extractionNamespace.namespaceParseSpec.listDelimiter",
|
|
||||||
"placeholder": "(optional)",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
Object {
|
Object {
|
||||||
"defined": [Function],
|
"defined": [Function],
|
||||||
"name": "extractionNamespace.namespaceParseSpec.keyFieldName",
|
"name": "extractionNamespace.namespaceParseSpec.keyFieldName",
|
||||||
@ -217,15 +256,9 @@ exports[`LookupEditDialog matches snapshot 1`] = `
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"defaultValue": "0",
|
|
||||||
"defined": [Function],
|
"defined": [Function],
|
||||||
"info": "Period between polling for updates",
|
"info": "Defines the connectURI for connecting to the database",
|
||||||
"name": "extractionNamespace.pollPeriod",
|
"issueWithValue": [Function],
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"defined": [Function],
|
|
||||||
"info": "Defines the connectURI value on the The connector config to used",
|
|
||||||
"label": "Connect URI",
|
"label": "Connect URI",
|
||||||
"name": "extractionNamespace.connectorConfig.connectURI",
|
"name": "extractionNamespace.connectorConfig.connectURI",
|
||||||
"required": true,
|
"required": true,
|
||||||
@ -243,12 +276,6 @@ exports[`LookupEditDialog matches snapshot 1`] = `
|
|||||||
"name": "extractionNamespace.connectorConfig.password",
|
"name": "extractionNamespace.connectorConfig.password",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
Object {
|
|
||||||
"defined": [Function],
|
|
||||||
"info": "Should tables be created",
|
|
||||||
"name": "extractionNamespace.connectorConfig.createTables",
|
|
||||||
"type": "boolean",
|
|
||||||
},
|
|
||||||
Object {
|
Object {
|
||||||
"defined": [Function],
|
"defined": [Function],
|
||||||
"info": <React.Fragment>
|
"info": <React.Fragment>
|
||||||
@ -264,7 +291,7 @@ exports[`LookupEditDialog matches snapshot 1`] = `
|
|||||||
</p>
|
</p>
|
||||||
</React.Fragment>,
|
</React.Fragment>,
|
||||||
"name": "extractionNamespace.table",
|
"name": "extractionNamespace.table",
|
||||||
"placeholder": "some_lookup_table",
|
"placeholder": "lookup_table",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
@ -283,7 +310,7 @@ exports[`LookupEditDialog matches snapshot 1`] = `
|
|||||||
</p>
|
</p>
|
||||||
</React.Fragment>,
|
</React.Fragment>,
|
||||||
"name": "extractionNamespace.keyColumn",
|
"name": "extractionNamespace.keyColumn",
|
||||||
"placeholder": "my_key_value",
|
"placeholder": "key_column",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
@ -302,28 +329,10 @@ exports[`LookupEditDialog matches snapshot 1`] = `
|
|||||||
</p>
|
</p>
|
||||||
</React.Fragment>,
|
</React.Fragment>,
|
||||||
"name": "extractionNamespace.valueColumn",
|
"name": "extractionNamespace.valueColumn",
|
||||||
"placeholder": "my_column_value",
|
"placeholder": "value_column",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string",
|
"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>,
|
|
||||||
"name": "extractionNamespace.filter",
|
|
||||||
"placeholder": "(optional)",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
Object {
|
Object {
|
||||||
"defined": [Function],
|
"defined": [Function],
|
||||||
"info": <React.Fragment>
|
"info": <React.Fragment>
|
||||||
@ -340,9 +349,42 @@ exports[`LookupEditDialog matches snapshot 1`] = `
|
|||||||
</React.Fragment>,
|
</React.Fragment>,
|
||||||
"label": "Timestamp column",
|
"label": "Timestamp column",
|
||||||
"name": "extractionNamespace.tsColumn",
|
"name": "extractionNamespace.tsColumn",
|
||||||
"placeholder": "(optional)",
|
"placeholder": "timestamp_column (optional)",
|
||||||
"type": "string",
|
"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>,
|
||||||
|
"name": "extractionNamespace.filter",
|
||||||
|
"placeholder": "for_lookup = 1 (optional)",
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"defined": [Function],
|
||||||
|
"info": "Period between polling for updates",
|
||||||
|
"name": "extractionNamespace.pollPeriod",
|
||||||
|
"required": true,
|
||||||
|
"suggestions": Array [
|
||||||
|
"PT1M",
|
||||||
|
"PT10M",
|
||||||
|
"PT30M",
|
||||||
|
"PT1H",
|
||||||
|
"PT6H",
|
||||||
|
"P1D",
|
||||||
|
],
|
||||||
|
"type": "duration",
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"defaultValue": 0,
|
"defaultValue": 0,
|
||||||
"defined": [Function],
|
"defined": [Function],
|
||||||
|
@ -28,7 +28,7 @@ describe('LookupEditDialog', () => {
|
|||||||
onClose={() => {}}
|
onClose={() => {}}
|
||||||
onSubmit={() => {}}
|
onSubmit={() => {}}
|
||||||
onChange={() => {}}
|
onChange={() => {}}
|
||||||
lookupName="test"
|
lookupId="test"
|
||||||
lookupTier="test"
|
lookupTier="test"
|
||||||
lookupVersion="test"
|
lookupVersion="test"
|
||||||
lookupSpec={{ type: 'map', map: { a: 1 } }}
|
lookupSpec={{ type: 'map', map: { a: 1 } }}
|
||||||
|
@ -36,10 +36,10 @@ export interface LookupEditDialogProps {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSubmit: (updateLookupVersion: boolean) => void;
|
onSubmit: (updateLookupVersion: boolean) => void;
|
||||||
onChange: (
|
onChange: (
|
||||||
field: 'name' | 'tier' | 'version' | 'spec',
|
field: 'id' | 'tier' | 'version' | 'spec',
|
||||||
value: string | Partial<LookupSpec>,
|
value: string | Partial<LookupSpec>,
|
||||||
) => void;
|
) => void;
|
||||||
lookupName: string;
|
lookupId: string;
|
||||||
lookupTier: string;
|
lookupTier: string;
|
||||||
lookupVersion: string;
|
lookupVersion: string;
|
||||||
lookupSpec: Partial<LookupSpec>;
|
lookupSpec: Partial<LookupSpec>;
|
||||||
@ -53,7 +53,7 @@ export const LookupEditDialog = React.memo(function LookupEditDialog(props: Look
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
lookupSpec,
|
lookupSpec,
|
||||||
lookupTier,
|
lookupTier,
|
||||||
lookupName,
|
lookupId,
|
||||||
lookupVersion,
|
lookupVersion,
|
||||||
onChange,
|
onChange,
|
||||||
isEdit,
|
isEdit,
|
||||||
@ -64,7 +64,7 @@ export const LookupEditDialog = React.memo(function LookupEditDialog(props: Look
|
|||||||
const [jsonError, setJsonError] = useState<Error | undefined>();
|
const [jsonError, setJsonError] = useState<Error | undefined>();
|
||||||
|
|
||||||
const disableSubmit = Boolean(
|
const disableSubmit = Boolean(
|
||||||
jsonError || isLookupInvalid(lookupName, lookupVersion, lookupTier, lookupSpec),
|
jsonError || isLookupInvalid(lookupId, lookupVersion, lookupTier, lookupSpec),
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -73,13 +73,14 @@ export const LookupEditDialog = React.memo(function LookupEditDialog(props: Look
|
|||||||
isOpen
|
isOpen
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={isEdit ? 'Edit lookup' : 'Add lookup'}
|
title={isEdit ? 'Edit lookup' : 'Add lookup'}
|
||||||
|
canEscapeKeyClose={false}
|
||||||
>
|
>
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<FormGroup label="Name">
|
<FormGroup label="Name">
|
||||||
<InputGroup
|
<InputGroup
|
||||||
value={lookupName}
|
value={lookupId}
|
||||||
onChange={(e: any) => onChange('name', e.target.value)}
|
onChange={(e: any) => onChange('id', e.target.value)}
|
||||||
intent={lookupName ? Intent.NONE : Intent.PRIMARY}
|
intent={lookupId ? Intent.NONE : Intent.PRIMARY}
|
||||||
disabled={isEdit}
|
disabled={isEdit}
|
||||||
placeholder="Enter the lookup name"
|
placeholder="Enter the lookup name"
|
||||||
/>
|
/>
|
||||||
@ -112,7 +113,7 @@ export const LookupEditDialog = React.memo(function LookupEditDialog(props: Look
|
|||||||
rightElement={
|
rightElement={
|
||||||
<Button
|
<Button
|
||||||
minimal
|
minimal
|
||||||
text="Use ISO as version"
|
text="Set to current ISO time"
|
||||||
onClick={() => onChange('version', new Date().toISOString())}
|
onClick={() => onChange('version', new Date().toISOString())}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@ -136,6 +137,7 @@ export const LookupEditDialog = React.memo(function LookupEditDialog(props: Look
|
|||||||
setJsonError(undefined);
|
setJsonError(undefined);
|
||||||
}}
|
}}
|
||||||
onError={setJsonError}
|
onError={setJsonError}
|
||||||
|
issueWithValue={spec => AutoForm.issueWithModel(spec, LOOKUP_FIELDS)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -232,7 +232,10 @@ exports[`retention dialog matches snapshot 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="bp3-input-group formatted-input-group suggestible-input"
|
class="formatted-input suggestible-input"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bp3-input-group"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
class="bp3-input"
|
class="bp3-input"
|
||||||
@ -274,6 +277,7 @@ exports[`retention dialog matches snapshot 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<label
|
<label
|
||||||
class="bp3-control bp3-switch include-future"
|
class="bp3-control bp3-switch include-future"
|
||||||
>
|
>
|
||||||
|
@ -32,6 +32,7 @@ export interface DimensionSpec {
|
|||||||
readonly type: string;
|
readonly type: string;
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly createBitmapIndex?: boolean;
|
readonly createBitmapIndex?: boolean;
|
||||||
|
readonly multiValueHandling?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DIMENSION_SPEC_FIELDS: Field<DimensionSpec>[] = [
|
export const DIMENSION_SPEC_FIELDS: Field<DimensionSpec>[] = [
|
||||||
@ -53,6 +54,13 @@ export const DIMENSION_SPEC_FIELDS: Field<DimensionSpec>[] = [
|
|||||||
defined: typeIs('string'),
|
defined: typeIs('string'),
|
||||||
defaultValue: true,
|
defaultValue: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'multiValueHandling',
|
||||||
|
type: 'string',
|
||||||
|
defined: typeIs('string'),
|
||||||
|
defaultValue: 'SORTED_ARRAY',
|
||||||
|
suggestions: ['SORTED_ARRAY', 'SORTED_SET', 'ARRAY'],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getDimensionSpecName(dimensionSpec: string | DimensionSpec): string {
|
export function getDimensionSpecName(dimensionSpec: string | DimensionSpec): string {
|
||||||
|
@ -156,8 +156,20 @@ describe('ingestion-spec', () => {
|
|||||||
expect(guessInputFormat(['A,B,X,Y']).type).toEqual('csv');
|
expect(guessInputFormat(['A,B,X,Y']).type).toEqual('csv');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('works for TSV with ;', () => {
|
||||||
|
const inputFormat = guessInputFormat(['A;B;X;Y']);
|
||||||
|
expect(inputFormat.type).toEqual('tsv');
|
||||||
|
expect(inputFormat.delimiter).toEqual(';');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works for TSV with |', () => {
|
||||||
|
const inputFormat = guessInputFormat(['A|B|X|Y']);
|
||||||
|
expect(inputFormat.type).toEqual('tsv');
|
||||||
|
expect(inputFormat.delimiter).toEqual('|');
|
||||||
|
});
|
||||||
|
|
||||||
it('works for regex', () => {
|
it('works for regex', () => {
|
||||||
expect(guessInputFormat(['A|B|X|Y']).type).toEqual('regex');
|
expect(guessInputFormat(['A/B/X/Y']).type).toEqual('regex');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2106,12 +2106,24 @@ export function guessInputFormat(sampleData: string[]): InputFormat {
|
|||||||
if (sampleDatum.split(',').length > 3) {
|
if (sampleDatum.split(',').length > 3) {
|
||||||
return inputFormatFromType('csv', !/,\d+,/.test(sampleDatum));
|
return inputFormatFromType('csv', !/,\d+,/.test(sampleDatum));
|
||||||
}
|
}
|
||||||
|
// Contains more than 3 semicolons assume semicolon separated
|
||||||
|
if (sampleDatum.split(';').length > 3) {
|
||||||
|
return inputFormatFromType('tsv', !/;\d+;/.test(sampleDatum), ';');
|
||||||
|
}
|
||||||
|
// Contains more than 3 pipes assume pipe separated
|
||||||
|
if (sampleDatum.split('|').length > 3) {
|
||||||
|
return inputFormatFromType('tsv', !/\|\d+\|/.test(sampleDatum), '|');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return inputFormatFromType('regex');
|
return inputFormatFromType('regex');
|
||||||
}
|
}
|
||||||
|
|
||||||
function inputFormatFromType(type: string, findColumnsFromHeader?: boolean): InputFormat {
|
function inputFormatFromType(
|
||||||
|
type: string,
|
||||||
|
findColumnsFromHeader?: boolean,
|
||||||
|
delimiter?: string,
|
||||||
|
): InputFormat {
|
||||||
let inputFormat: InputFormat = { type };
|
let inputFormat: InputFormat = { type };
|
||||||
|
|
||||||
if (type === 'regex') {
|
if (type === 'regex') {
|
||||||
@ -2123,6 +2135,10 @@ function inputFormatFromType(type: string, findColumnsFromHeader?: boolean): Inp
|
|||||||
inputFormat = deepSet(inputFormat, 'findColumnsFromHeader', findColumnsFromHeader);
|
inputFormat = deepSet(inputFormat, 'findColumnsFromHeader', findColumnsFromHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (delimiter) {
|
||||||
|
inputFormat = deepSet(inputFormat, 'delimiter', delimiter);
|
||||||
|
}
|
||||||
|
|
||||||
return inputFormat;
|
return inputFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ export interface InputFormat {
|
|||||||
readonly findColumnsFromHeader?: boolean;
|
readonly findColumnsFromHeader?: boolean;
|
||||||
readonly skipHeaderRows?: number;
|
readonly skipHeaderRows?: number;
|
||||||
readonly columns?: string[];
|
readonly columns?: string[];
|
||||||
|
readonly delimiter?: string;
|
||||||
readonly listDelimiter?: string;
|
readonly listDelimiter?: string;
|
||||||
readonly pattern?: string;
|
readonly pattern?: string;
|
||||||
readonly function?: string;
|
readonly function?: string;
|
||||||
@ -113,7 +114,7 @@ export const INPUT_FORMAT_FIELDS: Field<InputFormat>[] = [
|
|||||||
name: 'delimiter',
|
name: 'delimiter',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
defaultValue: '\t',
|
defaultValue: '\t',
|
||||||
suggestions: ['\t', '|', '#'],
|
suggestions: ['\t', ';', '|', '#'],
|
||||||
defined: typeIs('tsv'),
|
defined: typeIs('tsv'),
|
||||||
info: <>A custom delimiter for data values.</>,
|
info: <>A custom delimiter for data values.</>,
|
||||||
},
|
},
|
||||||
|
@ -342,6 +342,7 @@ describe('lookup-spec', () => {
|
|||||||
format: 'csv',
|
format: 'csv',
|
||||||
columns: ['key', 'value'],
|
columns: ['key', 'value'],
|
||||||
},
|
},
|
||||||
|
pollPeriod: 'PT1H',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
@ -359,6 +360,7 @@ describe('lookup-spec', () => {
|
|||||||
format: 'csv',
|
format: 'csv',
|
||||||
hasHeaderRow: true,
|
hasHeaderRow: true,
|
||||||
},
|
},
|
||||||
|
pollPeriod: 'PT1H',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
@ -376,6 +378,7 @@ describe('lookup-spec', () => {
|
|||||||
format: 'tsv',
|
format: 'tsv',
|
||||||
columns: ['key', 'value'],
|
columns: ['key', 'value'],
|
||||||
},
|
},
|
||||||
|
pollPeriod: 'PT1H',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
@ -394,6 +397,7 @@ describe('lookup-spec', () => {
|
|||||||
valueFieldName: 'value',
|
valueFieldName: 'value',
|
||||||
keyFieldName: 'value',
|
keyFieldName: 'value',
|
||||||
},
|
},
|
||||||
|
pollPeriod: 'PT1H',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
@ -416,7 +420,7 @@ describe('lookup-spec', () => {
|
|||||||
table: 'some_lookup_table',
|
table: 'some_lookup_table',
|
||||||
keyColumn: 'the_old_dim_value',
|
keyColumn: 'the_old_dim_value',
|
||||||
valueColumn: 'the_new_dim_value',
|
valueColumn: 'the_new_dim_value',
|
||||||
pollPeriod: 600000,
|
pollPeriod: 'PT1H',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
|
@ -20,7 +20,7 @@ import { Code } from '@blueprintjs/core';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { AutoForm, Field } from '../components';
|
import { AutoForm, Field } from '../components';
|
||||||
import { deepGet, deepSet, oneOf, typeIs } from '../utils';
|
import { deepGet, deepSet, oneOf, pluralIfNeeded, typeIs } from '../utils';
|
||||||
|
|
||||||
export interface ExtractionNamespaceSpec {
|
export interface ExtractionNamespaceSpec {
|
||||||
readonly type: string;
|
readonly type: string;
|
||||||
@ -63,6 +63,22 @@ export interface LookupSpec {
|
|||||||
readonly injective?: boolean;
|
readonly injective?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function issueWithUri(uri: string): string | undefined {
|
||||||
|
if (!uri) return;
|
||||||
|
const m = /^(\w+):/.exec(uri);
|
||||||
|
if (!m) return `URI is invalid, must start with 'file:', 'hdfs:', 's3:', or 'gs:`;
|
||||||
|
if (!oneOf(m[1], 'file', 'hdfs', 's3', 'gs')) {
|
||||||
|
return `Unsupported location '${m[1]}:'. Only 'file:', 'hdfs:', 's3:', and 'gs:' locations are supported`;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function issueWithConnectUri(uri: string): string | undefined {
|
||||||
|
if (!uri) return;
|
||||||
|
if (!uri.startsWith('jdbc:')) return `connectURI is invalid, must start with 'jdbc:'`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
export const LOOKUP_FIELDS: Field<LookupSpec>[] = [
|
export const LOOKUP_FIELDS: Field<LookupSpec>[] = [
|
||||||
{
|
{
|
||||||
name: 'type',
|
name: 'type',
|
||||||
@ -74,7 +90,7 @@ export const LOOKUP_FIELDS: Field<LookupSpec>[] = [
|
|||||||
return deepSet(l, 'map', {});
|
return deepSet(l, 'map', {});
|
||||||
}
|
}
|
||||||
if (l.type === 'cachedNamespace' && !deepGet(l, 'extractionNamespace.type')) {
|
if (l.type === 'cachedNamespace' && !deepGet(l, 'extractionNamespace.type')) {
|
||||||
return deepSet(l, 'extractionNamespace', { type: 'uri' });
|
return deepSet(l, 'extractionNamespace', { type: 'uri', pollPeriod: 'PT1H' });
|
||||||
}
|
}
|
||||||
return l;
|
return l;
|
||||||
},
|
},
|
||||||
@ -103,13 +119,14 @@ export const LOOKUP_FIELDS: Field<LookupSpec>[] = [
|
|||||||
// cachedNamespace lookups have more options
|
// cachedNamespace lookups have more options
|
||||||
{
|
{
|
||||||
name: 'extractionNamespace.type',
|
name: 'extractionNamespace.type',
|
||||||
label: 'Globally cached lookup type',
|
label: 'Extraction type',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
placeholder: 'uri',
|
placeholder: 'uri',
|
||||||
suggestions: ['uri', 'jdbc'],
|
suggestions: ['uri', 'jdbc'],
|
||||||
defined: typeIs('cachedNamespace'),
|
defined: typeIs('cachedNamespace'),
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'extractionNamespace.uriPrefix',
|
name: 'extractionNamespace.uriPrefix',
|
||||||
label: 'URI prefix',
|
label: 'URI prefix',
|
||||||
@ -119,8 +136,14 @@ export const LOOKUP_FIELDS: Field<LookupSpec>[] = [
|
|||||||
deepGet(l, 'extractionNamespace.type') === 'uri' && !deepGet(l, 'extractionNamespace.uri'),
|
deepGet(l, 'extractionNamespace.type') === 'uri' && !deepGet(l, 'extractionNamespace.uri'),
|
||||||
required: l =>
|
required: l =>
|
||||||
!deepGet(l, 'extractionNamespace.uriPrefix') && !deepGet(l, 'extractionNamespace.uri'),
|
!deepGet(l, 'extractionNamespace.uriPrefix') && !deepGet(l, 'extractionNamespace.uri'),
|
||||||
info:
|
issueWithValue: issueWithUri,
|
||||||
'A URI which specifies a directory (or other searchable resource) in which to search for files',
|
info: (
|
||||||
|
<p>
|
||||||
|
A URI which specifies a directory (or other searchable resource) in which to search for
|
||||||
|
files specified as a <Code>file</Code>, <Code>hdfs</Code>, <Code>s3</Code>, or{' '}
|
||||||
|
<Code>gs</Code> path prefix.
|
||||||
|
</p>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'extractionNamespace.uri',
|
name: 'extractionNamespace.uri',
|
||||||
@ -132,9 +155,13 @@ export const LOOKUP_FIELDS: Field<LookupSpec>[] = [
|
|||||||
!deepGet(l, 'extractionNamespace.uriPrefix'),
|
!deepGet(l, 'extractionNamespace.uriPrefix'),
|
||||||
required: l =>
|
required: l =>
|
||||||
!deepGet(l, 'extractionNamespace.uriPrefix') && !deepGet(l, 'extractionNamespace.uri'),
|
!deepGet(l, 'extractionNamespace.uriPrefix') && !deepGet(l, 'extractionNamespace.uri'),
|
||||||
|
issueWithValue: issueWithUri,
|
||||||
info: (
|
info: (
|
||||||
<>
|
<>
|
||||||
<p>URI for the file of interest, specified as a file, hdfs, or s3 path</p>
|
<p>
|
||||||
|
URI for the file of interest, specified as a <Code>file</Code>, <Code>hdfs</Code>,{' '}
|
||||||
|
<Code>s3</Code>, or <Code>gs</Code> path
|
||||||
|
</p>
|
||||||
<p>The URI prefix option is strictly better than URI and should be used instead</p>
|
<p>The URI prefix option is strictly better than URI and should be used instead</p>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
@ -170,32 +197,35 @@ export const LOOKUP_FIELDS: Field<LookupSpec>[] = [
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TSV only
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.namespaceParseSpec.delimiter',
|
||||||
|
type: 'string',
|
||||||
|
defaultValue: '\t',
|
||||||
|
suggestions: ['\t', ';', '|', '#'],
|
||||||
|
defined: l => deepGet(l, 'extractionNamespace.namespaceParseSpec.format') === 'tsv',
|
||||||
|
},
|
||||||
|
|
||||||
// CSV + TSV
|
// CSV + TSV
|
||||||
{
|
{
|
||||||
name: 'extractionNamespace.namespaceParseSpec.skipHeaderRows',
|
name: 'extractionNamespace.namespaceParseSpec.skipHeaderRows',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
defined: l =>
|
defined: l => oneOf(deepGet(l, 'extractionNamespace.namespaceParseSpec.format'), 'csv', 'tsv'),
|
||||||
deepGet(l, 'extractionNamespace.type') === 'uri' &&
|
info: `Number of header rows to be skipped.`,
|
||||||
oneOf(deepGet(l, 'extractionNamespace.namespaceParseSpec.format'), 'csv', 'tsv'),
|
|
||||||
info: `Number of header rows to be skipped. The default number of header rows to be skipped is 0.`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'extractionNamespace.namespaceParseSpec.hasHeaderRow',
|
name: 'extractionNamespace.namespaceParseSpec.hasHeaderRow',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
defined: l =>
|
defined: l => oneOf(deepGet(l, 'extractionNamespace.namespaceParseSpec.format'), 'csv', 'tsv'),
|
||||||
deepGet(l, 'extractionNamespace.type') === 'uri' &&
|
|
||||||
oneOf(deepGet(l, 'extractionNamespace.namespaceParseSpec.format'), 'csv', 'tsv'),
|
|
||||||
info: `A flag to indicate that column information can be extracted from the input files' header row`,
|
info: `A flag to indicate that column information can be extracted from the input files' header row`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'extractionNamespace.namespaceParseSpec.columns',
|
name: 'extractionNamespace.namespaceParseSpec.columns',
|
||||||
type: 'string-array',
|
type: 'string-array',
|
||||||
placeholder: `["key", "value"]`,
|
placeholder: 'key, value',
|
||||||
defined: l =>
|
defined: l => oneOf(deepGet(l, 'extractionNamespace.namespaceParseSpec.format'), 'csv', 'tsv'),
|
||||||
deepGet(l, 'extractionNamespace.type') === 'uri' &&
|
|
||||||
oneOf(deepGet(l, 'extractionNamespace.namespaceParseSpec.format'), 'csv', 'tsv'),
|
|
||||||
required: l => !deepGet(l, 'extractionNamespace.namespaceParseSpec.hasHeaderRow'),
|
required: l => !deepGet(l, 'extractionNamespace.namespaceParseSpec.hasHeaderRow'),
|
||||||
info: 'The list of columns in the csv file',
|
info: 'The list of columns in the csv file',
|
||||||
},
|
},
|
||||||
@ -203,65 +233,32 @@ export const LOOKUP_FIELDS: Field<LookupSpec>[] = [
|
|||||||
name: 'extractionNamespace.namespaceParseSpec.keyColumn',
|
name: 'extractionNamespace.namespaceParseSpec.keyColumn',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
placeholder: '(optional - defaults to the first column)',
|
placeholder: '(optional - defaults to the first column)',
|
||||||
defined: l =>
|
defined: l => oneOf(deepGet(l, 'extractionNamespace.namespaceParseSpec.format'), 'csv', 'tsv'),
|
||||||
deepGet(l, 'extractionNamespace.type') === 'uri' &&
|
|
||||||
oneOf(deepGet(l, 'extractionNamespace.namespaceParseSpec.format'), 'csv', 'tsv'),
|
|
||||||
info: 'The name of the column containing the key',
|
info: 'The name of the column containing the key',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'extractionNamespace.namespaceParseSpec.valueColumn',
|
name: 'extractionNamespace.namespaceParseSpec.valueColumn',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
placeholder: '(optional - defaults to the second column)',
|
placeholder: '(optional - defaults to the second column)',
|
||||||
defined: l =>
|
defined: l => oneOf(deepGet(l, 'extractionNamespace.namespaceParseSpec.format'), 'csv', 'tsv'),
|
||||||
deepGet(l, 'extractionNamespace.type') === 'uri' &&
|
|
||||||
oneOf(deepGet(l, 'extractionNamespace.namespaceParseSpec.format'), 'csv', 'tsv'),
|
|
||||||
info: 'The name of the column containing the value',
|
info: 'The name of the column containing the value',
|
||||||
},
|
},
|
||||||
|
|
||||||
// TSV only
|
|
||||||
{
|
|
||||||
name: 'extractionNamespace.namespaceParseSpec.delimiter',
|
|
||||||
type: 'string',
|
|
||||||
placeholder: `(optional)`,
|
|
||||||
defined: l =>
|
|
||||||
deepGet(l, 'extractionNamespace.type') === 'uri' &&
|
|
||||||
deepGet(l, 'extractionNamespace.namespaceParseSpec.format') === 'tsv',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'extractionNamespace.namespaceParseSpec.listDelimiter',
|
|
||||||
type: 'string',
|
|
||||||
placeholder: `(optional)`,
|
|
||||||
defined: l =>
|
|
||||||
deepGet(l, 'extractionNamespace.type') === 'uri' &&
|
|
||||||
deepGet(l, 'extractionNamespace.namespaceParseSpec.format') === 'tsv',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Custom JSON
|
// Custom JSON
|
||||||
{
|
{
|
||||||
name: 'extractionNamespace.namespaceParseSpec.keyFieldName',
|
name: 'extractionNamespace.namespaceParseSpec.keyFieldName',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
placeholder: `key`,
|
placeholder: `key`,
|
||||||
defined: l =>
|
defined: l => deepGet(l, 'extractionNamespace.namespaceParseSpec.format') === 'customJson',
|
||||||
deepGet(l, 'extractionNamespace.type') === 'uri' &&
|
|
||||||
deepGet(l, 'extractionNamespace.namespaceParseSpec.format') === 'customJson',
|
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'extractionNamespace.namespaceParseSpec.valueFieldName',
|
name: 'extractionNamespace.namespaceParseSpec.valueFieldName',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
placeholder: `value`,
|
placeholder: `value`,
|
||||||
defined: l =>
|
defined: l => deepGet(l, 'extractionNamespace.namespaceParseSpec.format') === 'customJson',
|
||||||
deepGet(l, 'extractionNamespace.type') === 'uri' &&
|
|
||||||
deepGet(l, 'extractionNamespace.namespaceParseSpec.format') === 'customJson',
|
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'extractionNamespace.pollPeriod',
|
|
||||||
type: 'string',
|
|
||||||
defaultValue: '0',
|
|
||||||
defined: l => oneOf(deepGet(l, 'extractionNamespace.type'), 'uri', 'jdbc'),
|
|
||||||
info: `Period between polling for updates`,
|
|
||||||
},
|
|
||||||
|
|
||||||
// JDBC stuff
|
// JDBC stuff
|
||||||
{
|
{
|
||||||
@ -270,7 +267,8 @@ export const LOOKUP_FIELDS: Field<LookupSpec>[] = [
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
defined: l => deepGet(l, 'extractionNamespace.type') === 'jdbc',
|
defined: l => deepGet(l, 'extractionNamespace.type') === 'jdbc',
|
||||||
required: true,
|
required: true,
|
||||||
info: 'Defines the connectURI value on the The connector config to used',
|
issueWithValue: issueWithConnectUri,
|
||||||
|
info: 'Defines the connectURI for connecting to the database',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'extractionNamespace.connectorConfig.user',
|
name: 'extractionNamespace.connectorConfig.user',
|
||||||
@ -284,16 +282,10 @@ export const LOOKUP_FIELDS: Field<LookupSpec>[] = [
|
|||||||
defined: l => deepGet(l, 'extractionNamespace.type') === 'jdbc',
|
defined: l => deepGet(l, 'extractionNamespace.type') === 'jdbc',
|
||||||
info: 'Defines the password to be used by the connector config',
|
info: 'Defines the password to be used by the connector config',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'extractionNamespace.connectorConfig.createTables',
|
|
||||||
type: 'boolean',
|
|
||||||
defined: l => deepGet(l, 'extractionNamespace.type') === 'jdbc',
|
|
||||||
info: 'Should tables be created',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'extractionNamespace.table',
|
name: 'extractionNamespace.table',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
placeholder: 'some_lookup_table',
|
placeholder: 'lookup_table',
|
||||||
defined: l => deepGet(l, 'extractionNamespace.type') === 'jdbc',
|
defined: l => deepGet(l, 'extractionNamespace.type') === 'jdbc',
|
||||||
required: true,
|
required: true,
|
||||||
info: (
|
info: (
|
||||||
@ -312,7 +304,7 @@ export const LOOKUP_FIELDS: Field<LookupSpec>[] = [
|
|||||||
{
|
{
|
||||||
name: 'extractionNamespace.keyColumn',
|
name: 'extractionNamespace.keyColumn',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
placeholder: 'my_key_value',
|
placeholder: 'key_column',
|
||||||
defined: l => deepGet(l, 'extractionNamespace.type') === 'jdbc',
|
defined: l => deepGet(l, 'extractionNamespace.type') === 'jdbc',
|
||||||
required: true,
|
required: true,
|
||||||
info: (
|
info: (
|
||||||
@ -331,7 +323,7 @@ export const LOOKUP_FIELDS: Field<LookupSpec>[] = [
|
|||||||
{
|
{
|
||||||
name: 'extractionNamespace.valueColumn',
|
name: 'extractionNamespace.valueColumn',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
placeholder: 'my_column_value',
|
placeholder: 'value_column',
|
||||||
defined: l => deepGet(l, 'extractionNamespace.type') === 'jdbc',
|
defined: l => deepGet(l, 'extractionNamespace.type') === 'jdbc',
|
||||||
required: true,
|
required: true,
|
||||||
info: (
|
info: (
|
||||||
@ -347,10 +339,29 @@ export const LOOKUP_FIELDS: Field<LookupSpec>[] = [
|
|||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'extractionNamespace.tsColumn',
|
||||||
|
type: 'string',
|
||||||
|
label: 'Timestamp column',
|
||||||
|
placeholder: 'timestamp_column (optional)',
|
||||||
|
defined: l => deepGet(l, 'extractionNamespace.type') === 'jdbc',
|
||||||
|
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>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'extractionNamespace.filter',
|
name: 'extractionNamespace.filter',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
placeholder: '(optional)',
|
placeholder: 'for_lookup = 1 (optional)',
|
||||||
defined: l => deepGet(l, 'extractionNamespace.type') === 'jdbc',
|
defined: l => deepGet(l, 'extractionNamespace.type') === 'jdbc',
|
||||||
info: (
|
info: (
|
||||||
<>
|
<>
|
||||||
@ -365,24 +376,14 @@ export const LOOKUP_FIELDS: Field<LookupSpec>[] = [
|
|||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'extractionNamespace.tsColumn',
|
name: 'extractionNamespace.pollPeriod',
|
||||||
type: 'string',
|
type: 'duration',
|
||||||
label: 'Timestamp column',
|
defined: l => oneOf(deepGet(l, 'extractionNamespace.type'), 'uri', 'jdbc'),
|
||||||
placeholder: '(optional)',
|
info: `Period between polling for updates`,
|
||||||
defined: l => deepGet(l, 'extractionNamespace.type') === 'jdbc',
|
required: true,
|
||||||
info: (
|
suggestions: ['PT1M', 'PT10M', 'PT30M', 'PT1H', 'PT6H', 'P1D'],
|
||||||
<>
|
|
||||||
<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>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Extra cachedNamespace things
|
// Extra cachedNamespace things
|
||||||
@ -403,15 +404,54 @@ export const LOOKUP_FIELDS: Field<LookupSpec>[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export function isLookupInvalid(
|
export function isLookupInvalid(
|
||||||
lookupName: string | undefined,
|
lookupId: string | undefined,
|
||||||
lookupVersion: string | undefined,
|
lookupVersion: string | undefined,
|
||||||
lookupTier: string | undefined,
|
lookupTier: string | undefined,
|
||||||
lookupSpec: Partial<LookupSpec>,
|
lookupSpec: Partial<LookupSpec>,
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
!lookupName ||
|
!lookupId || !lookupVersion || !lookupTier || !AutoForm.isValidModel(lookupSpec, LOOKUP_FIELDS)
|
||||||
!lookupVersion ||
|
|
||||||
!lookupTier ||
|
|
||||||
!AutoForm.isValidModel(lookupSpec, LOOKUP_FIELDS)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function lookupSpecSummary(spec: LookupSpec): string {
|
||||||
|
const { map, extractionNamespace } = spec;
|
||||||
|
|
||||||
|
if (map) {
|
||||||
|
return pluralIfNeeded(Object.keys(map).length, 'key');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extractionNamespace) {
|
||||||
|
switch (extractionNamespace.type) {
|
||||||
|
case 'uri':
|
||||||
|
if (extractionNamespace.uriPrefix) {
|
||||||
|
return `URI prefix: ${extractionNamespace.uriPrefix}, Match: ${
|
||||||
|
extractionNamespace.fileRegex || '.*'
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
if (extractionNamespace.uri) {
|
||||||
|
return `URI: ${extractionNamespace.uri}`;
|
||||||
|
}
|
||||||
|
return 'Unknown extractionNamespace lookup';
|
||||||
|
|
||||||
|
case 'jdbc': {
|
||||||
|
const columns = [
|
||||||
|
`${extractionNamespace.keyColumn} AS key`,
|
||||||
|
`${extractionNamespace.valueColumn} AS value`,
|
||||||
|
];
|
||||||
|
if (extractionNamespace.tsColumn) {
|
||||||
|
columns.push(`${extractionNamespace.tsColumn} AS ts`);
|
||||||
|
}
|
||||||
|
const queryParts = ['SELECT', columns.join(', '), `FROM ${extractionNamespace.table}`];
|
||||||
|
if (extractionNamespace.filter) {
|
||||||
|
queryParts.push(`WHERE ${extractionNamespace.filter}`);
|
||||||
|
}
|
||||||
|
return `${
|
||||||
|
extractionNamespace.connectorConfig?.connectURI || 'No connectURI'
|
||||||
|
} [${queryParts.join(' ')}]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Unknown lookup';
|
||||||
|
}
|
||||||
|
@ -35,7 +35,7 @@ const JSON_ESCAPES: Record<string, string> = {
|
|||||||
// The stringifier is just JSON minus the double quotes, the parser is much more forgiving
|
// The stringifier is just JSON minus the double quotes, the parser is much more forgiving
|
||||||
export const JSON_STRING_FORMATTER: Formatter<string> = {
|
export const JSON_STRING_FORMATTER: Formatter<string> = {
|
||||||
stringify: (str: string) => {
|
stringify: (str: string) => {
|
||||||
if (typeof str !== 'string') throw new TypeError(`must be a string`);
|
if (typeof str !== 'string') return '';
|
||||||
|
|
||||||
const json = JSON.stringify(str);
|
const json = JSON.stringify(str);
|
||||||
return json.substr(1, json.length - 2);
|
return json.substr(1, json.length - 2);
|
||||||
|
@ -28,3 +28,4 @@ export * from './object-change';
|
|||||||
export * from './query-cursor';
|
export * from './query-cursor';
|
||||||
export * from './query-manager';
|
export * from './query-manager';
|
||||||
export * from './query-state';
|
export * from './query-state';
|
||||||
|
export * from './sanitizers';
|
||||||
|
21
web-console/src/utils/sanitizers.ts
Normal file
21
web-console/src/utils/sanitizers.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function durationSanitizer(str: string): string {
|
||||||
|
return str.toUpperCase().replace(/[^0-9PYMDTHS.,]/g, '');
|
||||||
|
}
|
@ -19,7 +19,7 @@ exports[`FormEditor matches snapshot 1`] = `
|
|||||||
class="bp3-text-muted"
|
class="bp3-text-muted"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bp3-popover2-target"
|
class="info-popover bp3-popover2-target"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bp3-icon bp3-icon-info-sign"
|
class="bp3-icon bp3-icon-info-sign"
|
||||||
@ -47,7 +47,10 @@ exports[`FormEditor matches snapshot 1`] = `
|
|||||||
class="bp3-form-content"
|
class="bp3-form-content"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="bp3-input-group bp3-intent-primary formatted-input-group suggestible-input"
|
class="formatted-input suggestible-input"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bp3-input-group bp3-intent-primary"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
class="bp3-input"
|
class="bp3-input"
|
||||||
@ -59,6 +62,7 @@ exports[`FormEditor matches snapshot 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="apply-cancel-buttons"
|
class="apply-cancel-buttons"
|
||||||
>
|
>
|
||||||
|
@ -23,6 +23,8 @@ exports[`lookups view matches snapshot 1`] = `
|
|||||||
"Lookup tier",
|
"Lookup tier",
|
||||||
"Type",
|
"Type",
|
||||||
"Version",
|
"Version",
|
||||||
|
"Poll period",
|
||||||
|
"Summary",
|
||||||
"Actions",
|
"Actions",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -93,6 +95,7 @@ exports[`lookups view matches snapshot 1`] = `
|
|||||||
"filterable": true,
|
"filterable": true,
|
||||||
"id": "lookup_name",
|
"id": "lookup_name",
|
||||||
"show": true,
|
"show": true,
|
||||||
|
"width": 200,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"Header": "Lookup tier",
|
"Header": "Lookup tier",
|
||||||
@ -100,6 +103,7 @@ exports[`lookups view matches snapshot 1`] = `
|
|||||||
"filterable": true,
|
"filterable": true,
|
||||||
"id": "tier",
|
"id": "tier",
|
||||||
"show": true,
|
"show": true,
|
||||||
|
"width": 100,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"Header": "Type",
|
"Header": "Type",
|
||||||
@ -107,6 +111,7 @@ exports[`lookups view matches snapshot 1`] = `
|
|||||||
"filterable": true,
|
"filterable": true,
|
||||||
"id": "type",
|
"id": "type",
|
||||||
"show": true,
|
"show": true,
|
||||||
|
"width": 150,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"Header": "Version",
|
"Header": "Version",
|
||||||
@ -114,11 +119,26 @@ exports[`lookups view matches snapshot 1`] = `
|
|||||||
"filterable": true,
|
"filterable": true,
|
||||||
"id": "version",
|
"id": "version",
|
||||||
"show": true,
|
"show": true,
|
||||||
|
"width": 190,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"Cell": [Function],
|
||||||
|
"Header": "Poll period",
|
||||||
|
"accessor": [Function],
|
||||||
|
"id": "poolPeriod",
|
||||||
|
"show": true,
|
||||||
|
"width": 150,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"Header": "Summary",
|
||||||
|
"accessor": [Function],
|
||||||
|
"id": "summary",
|
||||||
|
"show": true,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"Cell": [Function],
|
"Cell": [Function],
|
||||||
"Header": "Actions",
|
"Header": "Actions",
|
||||||
"accessor": [Function],
|
"accessor": "id",
|
||||||
"filterable": false,
|
"filterable": false,
|
||||||
"id": "actions",
|
"id": "actions",
|
||||||
"show": true,
|
"show": true,
|
||||||
@ -135,7 +155,14 @@ exports[`lookups view matches snapshot 1`] = `
|
|||||||
defaultResized={Array []}
|
defaultResized={Array []}
|
||||||
defaultSortDesc={false}
|
defaultSortDesc={false}
|
||||||
defaultSortMethod={[Function]}
|
defaultSortMethod={[Function]}
|
||||||
defaultSorted={Array []}
|
defaultSorted={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"desc": false,
|
||||||
|
"id": "lookup_name",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
expanderDefaults={
|
expanderDefaults={
|
||||||
Object {
|
Object {
|
||||||
"filterable": false,
|
"filterable": false,
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Button, Intent } from '@blueprintjs/core';
|
import { Button, Icon, Intent } from '@blueprintjs/core';
|
||||||
import { IconNames } from '@blueprintjs/icons';
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactTable from 'react-table';
|
import ReactTable from 'react-table';
|
||||||
@ -32,9 +32,10 @@ import {
|
|||||||
} from '../../components';
|
} from '../../components';
|
||||||
import { AsyncActionDialog, LookupEditDialog } from '../../dialogs/';
|
import { AsyncActionDialog, LookupEditDialog } from '../../dialogs/';
|
||||||
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 { LookupSpec } from '../../druid-models';
|
import { LookupSpec, lookupSpecSummary } from '../../druid-models';
|
||||||
import { Api, AppToaster } from '../../singletons';
|
import { Api, AppToaster } from '../../singletons';
|
||||||
import {
|
import {
|
||||||
|
deepGet,
|
||||||
getDruidErrorMessage,
|
getDruidErrorMessage,
|
||||||
isLookupsUninitialized,
|
isLookupsUninitialized,
|
||||||
LocalStorageKeys,
|
LocalStorageKeys,
|
||||||
@ -51,6 +52,8 @@ const tableColumns: string[] = [
|
|||||||
'Lookup tier',
|
'Lookup tier',
|
||||||
'Type',
|
'Type',
|
||||||
'Version',
|
'Version',
|
||||||
|
'Poll period',
|
||||||
|
'Summary',
|
||||||
ACTION_COLUMN_LABEL,
|
ACTION_COLUMN_LABEL,
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -61,12 +64,19 @@ function tierNameCompare(a: string, b: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface LookupEntriesAndTiers {
|
export interface LookupEntriesAndTiers {
|
||||||
lookupEntries: any[];
|
lookupEntries: LookupEntry[];
|
||||||
tiers: string[];
|
tiers: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LookupEntry {
|
||||||
|
id: string;
|
||||||
|
tier: string;
|
||||||
|
version: string;
|
||||||
|
spec: LookupSpec;
|
||||||
|
}
|
||||||
|
|
||||||
export interface LookupEditInfo {
|
export interface LookupEditInfo {
|
||||||
name: string;
|
id: string;
|
||||||
tier: string;
|
tier: string;
|
||||||
version: string;
|
version: string;
|
||||||
spec: Partial<LookupSpec>;
|
spec: Partial<LookupSpec>;
|
||||||
@ -114,9 +124,10 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
|||||||
? tiersResp.data.sort(tierNameCompare)
|
? tiersResp.data.sort(tierNameCompare)
|
||||||
: [DEFAULT_LOOKUP_TIER];
|
: [DEFAULT_LOOKUP_TIER];
|
||||||
|
|
||||||
const lookupEntries: Record<string, string>[] = [];
|
|
||||||
const lookupResp = await Api.instance.get('/druid/coordinator/v1/lookups/config/all');
|
const lookupResp = await Api.instance.get('/druid/coordinator/v1/lookups/config/all');
|
||||||
const lookupData = lookupResp.data;
|
const lookupData = lookupResp.data;
|
||||||
|
|
||||||
|
const lookupEntries: LookupEntry[] = [];
|
||||||
Object.keys(lookupData).map((tier: string) => {
|
Object.keys(lookupData).map((tier: string) => {
|
||||||
const lookupIds = lookupData[tier];
|
const lookupIds = lookupData[tier];
|
||||||
Object.keys(lookupIds).map((id: string) => {
|
Object.keys(lookupIds).map((id: string) => {
|
||||||
@ -178,7 +189,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
|||||||
return {
|
return {
|
||||||
isEdit: false,
|
isEdit: false,
|
||||||
lookupEdit: {
|
lookupEdit: {
|
||||||
name: '',
|
id: '',
|
||||||
tier: loadingEntriesAndTiers ? loadingEntriesAndTiers.tiers[0] : '',
|
tier: loadingEntriesAndTiers ? loadingEntriesAndTiers.tiers[0] : '',
|
||||||
spec: { type: 'map', map: {} },
|
spec: { type: 'map', map: {} },
|
||||||
version: new Date().toISOString(),
|
version: new Date().toISOString(),
|
||||||
@ -189,8 +200,8 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
|||||||
this.setState({
|
this.setState({
|
||||||
isEdit: true,
|
isEdit: true,
|
||||||
lookupEdit: {
|
lookupEdit: {
|
||||||
name: id,
|
id,
|
||||||
tier: tier,
|
tier,
|
||||||
spec: target.spec,
|
spec: target.spec,
|
||||||
version: target.version,
|
version: target.version,
|
||||||
},
|
},
|
||||||
@ -216,7 +227,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
|||||||
const specJson: any = lookupEdit.spec;
|
const specJson: any = lookupEdit.spec;
|
||||||
let dataJson: any;
|
let dataJson: any;
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
endpoint = `${endpoint}/${lookupEdit.tier}/${lookupEdit.name}`;
|
endpoint = `${endpoint}/${lookupEdit.tier}/${lookupEdit.id}`;
|
||||||
dataJson = {
|
dataJson = {
|
||||||
version: version,
|
version: version,
|
||||||
lookupExtractorFactory: specJson,
|
lookupExtractorFactory: specJson,
|
||||||
@ -224,7 +235,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
|||||||
} else {
|
} else {
|
||||||
dataJson = {
|
dataJson = {
|
||||||
[lookupEdit.tier]: {
|
[lookupEdit.tier]: {
|
||||||
[lookupEdit.name]: {
|
[lookupEdit.id]: {
|
||||||
version: version,
|
version: version,
|
||||||
lookupExtractorFactory: specJson,
|
lookupExtractorFactory: specJson,
|
||||||
},
|
},
|
||||||
@ -319,6 +330,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
|||||||
: lookupEntriesAndTiersState.getErrorMessage() || ''
|
: lookupEntriesAndTiersState.getErrorMessage() || ''
|
||||||
}
|
}
|
||||||
filterable
|
filterable
|
||||||
|
defaultSorted={[{ id: 'lookup_name', desc: false }]}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
Header: 'Lookup name',
|
Header: 'Lookup name',
|
||||||
@ -326,6 +338,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
|||||||
id: 'lookup_name',
|
id: 'lookup_name',
|
||||||
accessor: 'id',
|
accessor: 'id',
|
||||||
filterable: true,
|
filterable: true,
|
||||||
|
width: 200,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Lookup tier',
|
Header: 'Lookup tier',
|
||||||
@ -333,6 +346,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
|||||||
id: 'tier',
|
id: 'tier',
|
||||||
accessor: 'tier',
|
accessor: 'tier',
|
||||||
filterable: true,
|
filterable: true,
|
||||||
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Type',
|
Header: 'Type',
|
||||||
@ -340,6 +354,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
|||||||
id: 'type',
|
id: 'type',
|
||||||
accessor: 'spec.type',
|
accessor: 'spec.type',
|
||||||
filterable: true,
|
filterable: true,
|
||||||
|
width: 150,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Version',
|
Header: 'Version',
|
||||||
@ -347,17 +362,44 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
|||||||
id: 'version',
|
id: 'version',
|
||||||
accessor: 'version',
|
accessor: 'version',
|
||||||
filterable: true,
|
filterable: true,
|
||||||
|
width: 190,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Poll period',
|
||||||
|
show: hiddenColumns.exists('Poll period'),
|
||||||
|
id: 'poolPeriod',
|
||||||
|
width: 150,
|
||||||
|
accessor: row => deepGet(row, 'spec.extractionNamespace.pollPeriod'),
|
||||||
|
Cell: ({ original }) => {
|
||||||
|
if (original.spec.type === 'map') return 'Static map';
|
||||||
|
const pollPeriod = deepGet(original, 'spec.extractionNamespace.pollPeriod');
|
||||||
|
if (!pollPeriod) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Icon icon={IconNames.WARNING_SIGN} intent={Intent.WARNING} /> No poll period
|
||||||
|
set
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return pollPeriod;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Summary',
|
||||||
|
show: hiddenColumns.exists('Summary'),
|
||||||
|
id: 'summary',
|
||||||
|
accessor: row => lookupSpecSummary(row.spec),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: ACTION_COLUMN_LABEL,
|
Header: ACTION_COLUMN_LABEL,
|
||||||
show: hiddenColumns.exists(ACTION_COLUMN_LABEL),
|
show: hiddenColumns.exists(ACTION_COLUMN_LABEL),
|
||||||
id: ACTION_COLUMN_ID,
|
id: ACTION_COLUMN_ID,
|
||||||
width: ACTION_COLUMN_WIDTH,
|
width: ACTION_COLUMN_WIDTH,
|
||||||
accessor: (row: any) => ({ id: row.id, tier: row.tier }),
|
|
||||||
filterable: false,
|
filterable: false,
|
||||||
Cell: (row: any) => {
|
accessor: 'id',
|
||||||
const lookupId = row.value.id;
|
Cell: ({ original }) => {
|
||||||
const lookupTier = row.value.tier;
|
const lookupId = original.id;
|
||||||
|
const lookupTier = original.tier;
|
||||||
const lookupActions = this.getLookupActions(lookupTier, lookupId);
|
const lookupActions = this.getLookupActions(lookupTier, lookupId);
|
||||||
return (
|
return (
|
||||||
<ActionCell
|
<ActionCell
|
||||||
@ -391,10 +433,10 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
|||||||
onClose={() => this.setState({ lookupEdit: undefined })}
|
onClose={() => this.setState({ lookupEdit: undefined })}
|
||||||
onSubmit={updateLookupVersion => this.submitLookupEdit(updateLookupVersion)}
|
onSubmit={updateLookupVersion => this.submitLookupEdit(updateLookupVersion)}
|
||||||
onChange={this.handleChangeLookup}
|
onChange={this.handleChangeLookup}
|
||||||
lookupSpec={lookupEdit.spec}
|
lookupId={lookupEdit.id}
|
||||||
lookupName={lookupEdit.name}
|
|
||||||
lookupTier={lookupEdit.tier}
|
lookupTier={lookupEdit.tier}
|
||||||
lookupVersion={lookupEdit.version}
|
lookupVersion={lookupEdit.version}
|
||||||
|
lookupSpec={lookupEdit.spec}
|
||||||
isEdit={isEdit}
|
isEdit={isEdit}
|
||||||
allLookupTiers={allLookupTiers}
|
allLookupTiers={allLookupTiers}
|
||||||
/>
|
/>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user