Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion web-console/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"test-e2e": "jest --config jest.e2e.config.js e2e-tests",
"codecov": "codecov --disable=gcov -p ..",
"coverage": "jest --coverage src",
"update-snapshots": "jest -u",
"update-snapshots": "jest -u --config jest.unit.config.js",
"autofix": "npm run eslint-fix && npm run sasslint-fix && npm run prettify",
"eslint": "eslint '{src,e2e-tests}/**/*.ts?(x)'",
"eslint-fix": "npm run eslint -- --fix",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// 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>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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 { render } from '@testing-library/react';
import React from 'react';

import { JSON_STRING_FORMATTER } from '../../utils';

import { FormattedInputGroup } from './formatted-input-group';

describe('FormattedInputGroup', () => {
it('matches snapshot on undefined value', () => {
const suggestibleInput = (
<FormattedInputGroup onValueChange={() => {}} formatter={JSON_STRING_FORMATTER} />
);

const { container } = render(suggestibleInput);
expect(container.firstChild).toMatchSnapshot();
});

it('matches snapshot with escaped value', () => {
const suggestibleInput = (
<FormattedInputGroup
value={`Here are some chars \t\r\n lol`}
onValueChange={() => {}}
formatter={JSON_STRING_FORMATTER}
/>
);

const { container } = render(suggestibleInput);
expect(container.firstChild).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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}
/>
);
});
1 change: 1 addition & 0 deletions web-console/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export * from './center-message/center-message';
export * from './clearable-input/clearable-input';
export * from './external-link/external-link';
export * from './form-json-selector/form-json-selector';
export * from './formatted-input-group/formatted-input-group';
export * from './header-bar/header-bar';
export * from './highlight-text/highlight-text';
export * from './json-collapse/json-collapse';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`suggestible input matches snapshot 1`] = `
exports[`SuggestibleInput matches snapshot 1`] = `
<div
class="bp3-input-group suggestible-input"
class="bp3-input-group formatted-input-group suggestible-input"
>
<input
class="bp3-input"
Expand Down Expand Up @@ -44,3 +44,48 @@ exports[`suggestible input matches snapshot 1`] = `
</span>
</div>
`;

exports[`SuggestibleInput matches snapshot with escaped value 1`] = `
<div
class="bp3-input-group formatted-input-group suggestible-input"
>
<input
class="bp3-input"
style="padding-right: 0px;"
type="text"
value="Here are some chars \\\\t\\\\r\\\\n lol"
/>
<span
class="bp3-input-action"
>
<span
class="bp3-popover2-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>
</div>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import React from 'react';

import { SuggestibleInput } from './suggestible-input';

describe('suggestible input', () => {
describe('SuggestibleInput', () => {
it('matches snapshot', () => {
const suggestibleInput = (
<SuggestibleInput onValueChange={() => {}} suggestions={['a', 'b', 'c']} />
Expand All @@ -30,4 +30,17 @@ describe('suggestible input', () => {
const { container } = render(suggestibleInput);
expect(container.firstChild).toMatchSnapshot();
});

it('matches snapshot with escaped value', () => {
const suggestibleInput = (
<SuggestibleInput
value={`Here are some chars \t\r\n lol`}
onValueChange={() => {}}
suggestions={['a', 'b', 'c']}
/>
);

const { container } = render(suggestibleInput);
expect(container.firstChild).toMatchSnapshot();
});
});
44 changes: 19 additions & 25 deletions web-console/src/components/suggestible-input/suggestible-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,38 @@
* limitations under the License.
*/

import {
Button,
HTMLInputProps,
InputGroup,
Intent,
Menu,
MenuItem,
Position,
} from '@blueprintjs/core';
import { Button, Menu, MenuItem, Position } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import { Popover2 } from '@blueprintjs/popover2';
import classNames from 'classnames';
import React, { useRef } from 'react';

import { JSON_STRING_FORMATTER } from '../../utils';
import {
FormattedInputGroup,
FormattedInputGroupProps,
} from '../formatted-input-group/formatted-input-group';

export interface SuggestionGroup {
group: string;
suggestions: string[];
}

export type Suggestion = undefined | string | SuggestionGroup;

export interface SuggestibleInputProps extends HTMLInputProps {
onValueChange: (newValue: undefined | string) => void;
export interface SuggestibleInputProps extends Omit<FormattedInputGroupProps, 'formatter'> {
onFinalize?: () => void;
suggestions?: Suggestion[];
large?: boolean;
intent?: Intent;
}

export const SuggestibleInput = React.memo(function SuggestibleInput(props: SuggestibleInputProps) {
const {
className,
value,
defaultValue,
onValueChange,
onFinalize,
onBlur,
onFocus,
suggestions,
...rest
} = props;
Expand All @@ -65,20 +60,19 @@ export const SuggestibleInput = React.memo(function SuggestibleInput(props: Sugg
}

return (
<InputGroup
<FormattedInputGroup
className={classNames('suggestible-input', className)}
value={value as string}
defaultValue={defaultValue as string}
onChange={(e: any) => {
onValueChange(e.target.value);
}}
onFocus={(e: any) => {
formatter={JSON_STRING_FORMATTER}
value={value}
onValueChange={onValueChange}
onFocus={e => {
lastFocusValue.current = e.target.value;
onFocus?.(e);
}}
onBlur={(e: any) => {
if (onBlur) onBlur(e);
onBlur={e => {
onBlur?.(e);
if (lastFocusValue.current === e.target.value) return;
if (onFinalize) onFinalize();
onFinalize?.();
}}
rightElement={
suggestions && (
Expand All @@ -98,7 +92,7 @@ export const SuggestibleInput = React.memo(function SuggestibleInput(props: Sugg
return (
<MenuItem
key={suggestion}
text={suggestion}
text={JSON_STRING_FORMATTER.stringify(suggestion)}
onClick={() => handleSuggestionSelect(suggestion)}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ exports[`retention dialog matches snapshot 1`] = `
</span>
</div>
<div
class="bp3-input-group suggestible-input"
class="bp3-input-group formatted-input-group suggestible-input"
>
<input
class="bp3-input"
Expand Down
4 changes: 3 additions & 1 deletion web-console/src/druid-models/input-format.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,16 @@ export const INPUT_FORMAT_FIELDS: Field<InputFormat>[] = [
name: 'delimiter',
type: 'string',
defaultValue: '\t',
suggestions: ['\t', '|', '#'],
defined: (p: InputFormat) => p.type === 'tsv',
info: <>A custom delimiter for data values.</>,
},
{
name: 'listDelimiter',
type: 'string',
defaultValue: '\x01',
suggestions: ['\x01', '\x00'],
defined: (p: InputFormat) => oneOf(p.type, 'csv', 'tsv', 'regex'),
placeholder: '(optional, default = ctrl+A)',
info: <>A custom delimiter for multi-value dimensions.</>,
},
{
Expand Down
Loading