mirror of https://github.com/apache/druid.git
Web console: Improve data loader styling, enforce stricter TS types (#8001)
* add assets to auth exclude path * add frame to tile page * better empty filter state * strict TS * fix segments go to sql * add unavailable segments * factor out sugestable input * fix tests * update datasources sql * no depricated extend * add index spec to tuning configs * fix scss lint
This commit is contained in:
parent
2831944056
commit
f16f13cf61
|
@ -65,6 +65,7 @@ public class RouterJettyServerInitializer implements JettyServerInitializer
|
||||||
protected static final List<String> UNSECURED_PATHS_FOR_UI = ImmutableList.of(
|
protected static final List<String> UNSECURED_PATHS_FOR_UI = ImmutableList.of(
|
||||||
"/",
|
"/",
|
||||||
"/coordinator-console/*",
|
"/coordinator-console/*",
|
||||||
|
"/assets/*",
|
||||||
"/public/*",
|
"/public/*",
|
||||||
"/old-console/*",
|
"/old-console/*",
|
||||||
"/pages/*",
|
"/pages/*",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "web-console",
|
"name": "web-console",
|
||||||
"version": "0.15.0",
|
"version": "0.16.0",
|
||||||
"description": "A web console for Apache Druid",
|
"description": "A web console for Apache Druid",
|
||||||
"author": "Imply Data Inc.",
|
"author": "Imply Data Inc.",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
"compile": "./script/build",
|
"compile": "./script/build",
|
||||||
"pretest": "./script/build",
|
"pretest": "./script/build",
|
||||||
"run": "./script/run",
|
"run": "./script/run",
|
||||||
"test": "npm run tslint && jest --silent 2>&1",
|
"test": "npm run tslint && npm run stylelint && jest --silent 2>&1",
|
||||||
"coverage": "jest --coverage",
|
"coverage": "jest --coverage",
|
||||||
"update-snapshots": "jest -u",
|
"update-snapshots": "jest -u",
|
||||||
"tslint": "./node_modules/.bin/tslint -c tslint.json --project tsconfig.json --formatters-dir ./node_modules/awesome-code-style/formatter 'src/**/*.ts?(x)'",
|
"tslint": "./node_modules/.bin/tslint -c tslint.json --project tsconfig.json --formatters-dir ./node_modules/awesome-code-style/formatter 'src/**/*.ts?(x)'",
|
||||||
|
|
|
@ -85,7 +85,7 @@ import React from 'react';
|
||||||
|
|
||||||
import './${name}.scss';
|
import './${name}.scss';
|
||||||
|
|
||||||
export interface ${camelName}Props extends React.Props<any> {
|
export interface ${camelName}Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ${camelName}State {
|
export interface ${camelName}State {
|
||||||
|
@ -100,7 +100,7 @@ export class ${camelName} extends React.PureComponent<${camelName}Props, ${camel
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div className="${name}">
|
return <div className="${name}">
|
||||||
|
Stuff...
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import React from 'react';
|
||||||
|
|
||||||
import './react-table-custom-pagination.scss';
|
import './react-table-custom-pagination.scss';
|
||||||
|
|
||||||
interface ReactTableCustomPaginationProps extends React.Props<any> {
|
interface ReactTableCustomPaginationProps {
|
||||||
pages: number;
|
pages: number;
|
||||||
page: number;
|
page: number;
|
||||||
showPageSizeOptions: boolean;
|
showPageSizeOptions: boolean;
|
||||||
|
|
|
@ -38,7 +38,7 @@ class NoData extends React.PureComponent {
|
||||||
|
|
||||||
Object.assign(ReactTableDefaults, {
|
Object.assign(ReactTableDefaults, {
|
||||||
className: '-striped -highlight',
|
className: '-striped -highlight',
|
||||||
defaultFilterMethod: (filter: Filter, row: any, column: any) => {
|
defaultFilterMethod: (filter: Filter, row: any) => {
|
||||||
const id = filter.pivotId || filter.id;
|
const id = filter.pivotId || filter.id;
|
||||||
return booleanCustomTableFilter(filter, row[id]);
|
return booleanCustomTableFilter(filter, row[id]);
|
||||||
},
|
},
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { ActionIcon } from '../action-icon/action-icon';
|
||||||
|
|
||||||
import './action-cell.scss';
|
import './action-cell.scss';
|
||||||
|
|
||||||
export interface ActionCellProps extends React.Props<any> {
|
export interface ActionCellProps {
|
||||||
onDetail?: () => void;
|
onDetail?: () => void;
|
||||||
actions?: BasicAction[];
|
actions?: BasicAction[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ import React from 'react';
|
||||||
|
|
||||||
import './action-icon.scss';
|
import './action-icon.scss';
|
||||||
|
|
||||||
export interface ActionIconProps extends React.Props<any> {
|
export interface ActionIconProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
icon: IconName;
|
icon: IconName;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
|
|
|
@ -190,7 +190,7 @@ exports[`auto-form snapshot matches snapshot 1`] = `
|
||||||
class="bp3-form-content"
|
class="bp3-form-content"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="bp3-input-group"
|
class="bp3-input-group suggestible-input"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
class="bp3-input"
|
class="bp3-input"
|
||||||
|
|
|
@ -35,7 +35,7 @@ describe('auto-form snapshot', () => {
|
||||||
{ name: 'testSeven', type: 'json' },
|
{ name: 'testSeven', type: 'json' },
|
||||||
]}
|
]}
|
||||||
model={String}
|
model={String}
|
||||||
onChange={(newModel: Record<string, any>) => {}}
|
onChange={() => {}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const { container } = render(autoForm);
|
const { container } = render(autoForm);
|
||||||
|
|
|
@ -16,32 +16,17 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { FormGroup, HTMLSelect, Icon, NumericInput, Popover } from '@blueprintjs/core';
|
||||||
Button,
|
|
||||||
FormGroup,
|
|
||||||
HTMLSelect,
|
|
||||||
Icon,
|
|
||||||
InputGroup,
|
|
||||||
Menu,
|
|
||||||
MenuItem,
|
|
||||||
NumericInput,
|
|
||||||
Popover,
|
|
||||||
Position,
|
|
||||||
} from '@blueprintjs/core';
|
|
||||||
import { IconNames } from '@blueprintjs/icons';
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { deepDelete, deepGet, deepSet } from '../../utils/object-change';
|
import { deepDelete, deepGet, deepSet } from '../../utils/object-change';
|
||||||
import { ArrayInput } from '../array-input/array-input';
|
import { ArrayInput } from '../array-input/array-input';
|
||||||
import { JSONInput } from '../json-input/json-input';
|
import { JSONInput } from '../json-input/json-input';
|
||||||
|
import { SuggestibleInput, SuggestionGroup } from '../suggestible-input/suggestible-input';
|
||||||
|
|
||||||
import './auto-form.scss';
|
import './auto-form.scss';
|
||||||
|
|
||||||
export interface SuggestionGroup {
|
|
||||||
group: string;
|
|
||||||
suggestions: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Field<T> {
|
export interface Field<T> {
|
||||||
name: string;
|
name: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
|
@ -55,7 +40,7 @@ export interface Field<T> {
|
||||||
min?: number;
|
min?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AutoFormProps<T> extends React.Props<any> {
|
export interface AutoFormProps<T> {
|
||||||
fields: Field<T>[];
|
fields: Field<T>[];
|
||||||
model: T | null;
|
model: T | null;
|
||||||
onChange: (newModel: T) => void;
|
onChange: (newModel: T) => void;
|
||||||
|
@ -64,13 +49,13 @@ export interface AutoFormProps<T> extends React.Props<any> {
|
||||||
large?: boolean;
|
large?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AutoFormState<T> {
|
export interface AutoFormState {
|
||||||
jsonInputsValidity: any;
|
jsonInputsValidity: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AutoForm<T extends Record<string, any>> extends React.PureComponent<
|
export class AutoForm<T extends Record<string, any>> extends React.PureComponent<
|
||||||
AutoFormProps<T>,
|
AutoFormProps<T>,
|
||||||
AutoFormState<T>
|
AutoFormState
|
||||||
> {
|
> {
|
||||||
static makeLabelName(label: string): string {
|
static makeLabelName(label: string): string {
|
||||||
let newLabel = label
|
let newLabel = label
|
||||||
|
@ -159,42 +144,11 @@ 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>, sanitize?: (str: string) => string): JSX.Element {
|
||||||
const { model, large } = this.props;
|
const { model, large } = this.props;
|
||||||
|
|
||||||
const suggestionsMenu = field.suggestions ? (
|
|
||||||
<Menu>
|
|
||||||
{field.suggestions.map(suggestion => {
|
|
||||||
if (typeof suggestion === 'string') {
|
|
||||||
return (
|
|
||||||
<MenuItem
|
|
||||||
key={suggestion}
|
|
||||||
text={suggestion}
|
|
||||||
onClick={() => this.fieldChange(field, suggestion)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<MenuItem key={suggestion.group} text={suggestion.group}>
|
|
||||||
{suggestion.suggestions.map(suggestion => (
|
|
||||||
<MenuItem
|
|
||||||
key={suggestion}
|
|
||||||
text={suggestion}
|
|
||||||
onClick={() => this.fieldChange(field, suggestion)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</MenuItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</Menu>
|
|
||||||
) : (
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
const modalValue = deepGet(model as any, field.name);
|
const modalValue = deepGet(model as any, field.name);
|
||||||
return (
|
return (
|
||||||
<InputGroup
|
<SuggestibleInput
|
||||||
value={modalValue != null ? modalValue : field.defaultValue || ''}
|
value={modalValue != null ? modalValue : field.defaultValue || ''}
|
||||||
onChange={(e: any) => {
|
onValueChange={v => {
|
||||||
let v = e.target.value;
|
|
||||||
if (sanitize) v = sanitize(v);
|
if (sanitize) v = sanitize(v);
|
||||||
this.fieldChange(field, v);
|
this.fieldChange(field, v);
|
||||||
}}
|
}}
|
||||||
|
@ -202,13 +156,7 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
|
||||||
if (modalValue === '') this.fieldChange(field, undefined);
|
if (modalValue === '') this.fieldChange(field, undefined);
|
||||||
}}
|
}}
|
||||||
placeholder={field.placeholder}
|
placeholder={field.placeholder}
|
||||||
rightElement={
|
suggestions={field.suggestions}
|
||||||
suggestionsMenu && (
|
|
||||||
<Popover content={suggestionsMenu} position={Position.BOTTOM_RIGHT} autoFocus={false}>
|
|
||||||
<Button icon={IconNames.CARET_DOWN} minimal />
|
|
||||||
</Popover>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
large={large}
|
large={large}
|
||||||
disabled={field.disabled}
|
disabled={field.disabled}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -20,7 +20,7 @@ import React from 'react';
|
||||||
|
|
||||||
import './center-message.scss';
|
import './center-message.scss';
|
||||||
|
|
||||||
export interface CenterMessageProps extends React.Props<any> {}
|
export interface CenterMessageProps {}
|
||||||
|
|
||||||
export class CenterMessage extends React.PureComponent<CenterMessageProps> {
|
export class CenterMessage extends React.PureComponent<CenterMessageProps> {
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`decribe clearable-input matches snapshot 1`] = `
|
exports[`clearable-input matches snapshot 1`] = `
|
||||||
<div
|
<div
|
||||||
class="bp3-input-group clearable-input testClassName"
|
class="bp3-input-group clearable-input testClassName"
|
||||||
>
|
>
|
||||||
|
|
|
@ -21,16 +21,16 @@ import { render } from 'react-testing-library';
|
||||||
|
|
||||||
import { ClearableInput } from './clearable-input';
|
import { ClearableInput } from './clearable-input';
|
||||||
|
|
||||||
describe('decribe clearable-input', () => {
|
describe('clearable-input', () => {
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
const centerMessage = (
|
const centerMessage = (
|
||||||
<ClearableInput
|
<ClearableInput
|
||||||
className={'testClassName'}
|
className={'testClassName'}
|
||||||
value={'testValue'}
|
value={'testValue'}
|
||||||
placeholder={'testPlaceholder'}
|
placeholder={'testPlaceholder'}
|
||||||
onChange={(value: string) => null}
|
onChange={() => null}
|
||||||
>
|
>
|
||||||
;<div>Hello World</div>
|
<div>Hello World</div>
|
||||||
</ClearableInput>
|
</ClearableInput>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { IconNames } from '@blueprintjs/icons';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export interface ClearableInputProps extends React.Props<any> {
|
export interface ClearableInputProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
value: string;
|
value: string;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export interface ExternalLinkProps extends React.Props<any> {
|
export interface ExternalLinkProps {
|
||||||
href: string;
|
href: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,6 @@ import {
|
||||||
Button,
|
Button,
|
||||||
Intent,
|
Intent,
|
||||||
Menu,
|
Menu,
|
||||||
MenuDivider,
|
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Navbar,
|
Navbar,
|
||||||
NavbarDivider,
|
NavbarDivider,
|
||||||
|
@ -34,8 +33,8 @@ import { IconNames } from '@blueprintjs/icons';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { AboutDialog } from '../../dialogs/about-dialog/about-dialog';
|
import { AboutDialog } from '../../dialogs/about-dialog/about-dialog';
|
||||||
import { CoordinatorDynamicConfigDialog } from '../../dialogs/coordinator-dynamic-config/coordinator-dynamic-config';
|
import { CoordinatorDynamicConfigDialog } from '../../dialogs/coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog';
|
||||||
import { OverlordDynamicConfigDialog } from '../../dialogs/overlord-dynamic-config/overlord-dynamic-config';
|
import { OverlordDynamicConfigDialog } from '../../dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog';
|
||||||
import {
|
import {
|
||||||
DRUID_DOCS,
|
DRUID_DOCS,
|
||||||
DRUID_GITHUB,
|
DRUID_GITHUB,
|
||||||
|
@ -56,7 +55,7 @@ export type HeaderActiveTab =
|
||||||
| 'servers'
|
| 'servers'
|
||||||
| 'lookups';
|
| 'lookups';
|
||||||
|
|
||||||
export interface HeaderBarProps extends React.Props<any> {
|
export interface HeaderBarProps {
|
||||||
active: HeaderActiveTab;
|
active: HeaderActiveTab;
|
||||||
hideLegacy: boolean;
|
hideLegacy: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Button, Collapse, TextArea } from '@blueprintjs/core';
|
import { Button, Collapse, TextArea } from '@blueprintjs/core';
|
||||||
import classNames from 'classnames';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
interface JSONCollapseProps extends React.Props<any> {
|
interface JSONCollapseProps {
|
||||||
stringValue: string;
|
stringValue: string;
|
||||||
buttonText: string;
|
buttonText: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { JSONInput } from './json-input';
|
||||||
|
|
||||||
describe('json input', () => {
|
describe('json input', () => {
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
const jsonCollapse = <JSONInput onChange={(newJSONValue: any) => {}} value={'test'} />;
|
const jsonCollapse = <JSONInput onChange={() => {}} value={'test'} />;
|
||||||
const { container } = render(jsonCollapse);
|
const { container } = render(jsonCollapse);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(container.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,7 +21,7 @@ import AceEditor from 'react-ace';
|
||||||
|
|
||||||
import { parseStringToJSON, stringifyJSON, validJson } from '../../utils';
|
import { parseStringToJSON, stringifyJSON, validJson } from '../../utils';
|
||||||
|
|
||||||
interface JSONInputProps extends React.Props<any> {
|
interface JSONInputProps {
|
||||||
onChange: (newJSONValue: any) => void;
|
onChange: (newJSONValue: any) => void;
|
||||||
value: any;
|
value: any;
|
||||||
updateInputValidity?: (valueValid: boolean) => void;
|
updateInputValidity?: (valueValid: boolean) => void;
|
||||||
|
|
|
@ -20,7 +20,7 @@ import React from 'react';
|
||||||
|
|
||||||
import './loader.scss';
|
import './loader.scss';
|
||||||
|
|
||||||
export interface LoaderProps extends React.Props<any> {
|
export interface LoaderProps {
|
||||||
loadingText?: string;
|
loadingText?: string;
|
||||||
loading?: boolean; // This is needed so that this component can be used as a LoadingComponent in react table
|
loading?: boolean; // This is needed so that this component can be used as a LoadingComponent in react table
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import React from 'react';
|
||||||
import { LocalStorageKeys } from '../../utils';
|
import { LocalStorageKeys } from '../../utils';
|
||||||
import { TimedButton } from '../timed-button/timed-button';
|
import { TimedButton } from '../timed-button/timed-button';
|
||||||
|
|
||||||
export interface RefreshButtonProps extends React.Props<any> {
|
export interface RefreshButtonProps {
|
||||||
onRefresh: (auto: boolean) => void;
|
onRefresh: (auto: boolean) => void;
|
||||||
localStorageKey?: LocalStorageKeys;
|
localStorageKey?: LocalStorageKeys;
|
||||||
}
|
}
|
||||||
|
@ -45,10 +45,10 @@ export class RefreshButton extends React.PureComponent<RefreshButtonProps> {
|
||||||
return (
|
return (
|
||||||
<TimedButton
|
<TimedButton
|
||||||
defaultValue={30000}
|
defaultValue={30000}
|
||||||
label={'Auto refresh every:'}
|
label="Auto refresh every:"
|
||||||
intervals={intervals}
|
intervals={intervals}
|
||||||
icon={IconNames.REFRESH}
|
icon={IconNames.REFRESH}
|
||||||
text={'Refresh'}
|
text="Refresh"
|
||||||
onRefresh={onRefresh}
|
onRefresh={onRefresh}
|
||||||
localStorageKey={localStorageKey}
|
localStorageKey={localStorageKey}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-testing-library';
|
import { render } from 'react-testing-library';
|
||||||
|
|
||||||
import { Rule, RuleEditor } from './rule-editor';
|
import { RuleEditor } from './rule-editor';
|
||||||
|
|
||||||
describe('rule editor', () => {
|
describe('rule editor', () => {
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
|
@ -27,7 +27,7 @@ describe('rule editor', () => {
|
||||||
<RuleEditor
|
<RuleEditor
|
||||||
rule={{ type: 'loadForever' }}
|
rule={{ type: 'loadForever' }}
|
||||||
tiers={['test', 'test', 'test']}
|
tiers={['test', 'test', 'test']}
|
||||||
onChange={(newRule: Rule) => null}
|
onChange={() => null}
|
||||||
onDelete={() => null}
|
onDelete={() => null}
|
||||||
moveUp={null}
|
moveUp={null}
|
||||||
moveDown={null}
|
moveDown={null}
|
||||||
|
|
|
@ -28,7 +28,6 @@ import {
|
||||||
TagInput,
|
TagInput,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { IconNames } from '@blueprintjs/icons';
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
import axios from 'axios';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import './rule-editor.scss';
|
import './rule-editor.scss';
|
||||||
|
@ -53,7 +52,7 @@ export interface Rule {
|
||||||
export type LoadType = 'load' | 'drop' | 'broadcast';
|
export type LoadType = 'load' | 'drop' | 'broadcast';
|
||||||
export type TimeType = 'Forever' | 'ByInterval' | 'ByPeriod';
|
export type TimeType = 'Forever' | 'ByInterval' | 'ByPeriod';
|
||||||
|
|
||||||
export interface RuleEditorProps extends React.Props<any> {
|
export interface RuleEditorProps {
|
||||||
rule: Rule;
|
rule: Rule;
|
||||||
tiers: any[];
|
tiers: any[];
|
||||||
onChange: (newRule: Rule) => void;
|
onChange: (newRule: Rule) => void;
|
||||||
|
@ -241,7 +240,7 @@ export class RuleEditor extends React.PureComponent<RuleEditorProps, RuleEditorS
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { tiers, onChange, rule, onDelete, moveUp, moveDown } = this.props;
|
const { onChange, rule, onDelete, moveUp, moveDown } = this.props;
|
||||||
const { isOpen } = this.state;
|
const { isOpen } = this.state;
|
||||||
|
|
||||||
if (!rule) return null;
|
if (!rule) return null;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Button, ButtonGroup, InputGroup, Intent, TextArea } from '@blueprintjs/core';
|
import { Button, ButtonGroup, Intent, TextArea } from '@blueprintjs/core';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import copy from 'copy-to-clipboard';
|
import copy from 'copy-to-clipboard';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
@ -27,7 +27,7 @@ import { downloadFile } from '../../utils';
|
||||||
|
|
||||||
import './show-json.scss';
|
import './show-json.scss';
|
||||||
|
|
||||||
export interface ShowJsonProps extends React.Props<any> {
|
export interface ShowJsonProps {
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
transform?: (x: any) => any;
|
transform?: (x: any) => any;
|
||||||
downloadFilename?: string;
|
downloadFilename?: string;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Button, ButtonGroup, Checkbox, InputGroup, Intent, TextArea } from '@blueprintjs/core';
|
import { Button, ButtonGroup, Checkbox, Intent } from '@blueprintjs/core';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import copy from 'copy-to-clipboard';
|
import copy from 'copy-to-clipboard';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
@ -35,7 +35,7 @@ function removeFirstPartialLine(log: string): string {
|
||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ShowLogProps extends React.Props<any> {
|
export interface ShowLogProps {
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
downloadFilename?: string;
|
downloadFilename?: string;
|
||||||
tailOffset?: number;
|
tailOffset?: number;
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`suggestible input matches snapshot 1`] = `
|
||||||
|
<div
|
||||||
|
class="bp3-input-group suggestible-input"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="bp3-input"
|
||||||
|
style="padding-right: 0px;"
|
||||||
|
suggestions="a,b,c"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<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 0 0-.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>
|
||||||
|
`;
|
|
@ -19,25 +19,15 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-testing-library';
|
import { render } from 'react-testing-library';
|
||||||
|
|
||||||
import { LookupEditDialog } from '../lookup-edit-dialog/lookup-edit-dialog';
|
import { SuggestibleInput } from './suggestible-input';
|
||||||
|
|
||||||
describe('overload dynamic config', () => {
|
describe('suggestible input', () => {
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
const lookupEditDialog = (
|
const suggestibleInput = (
|
||||||
<LookupEditDialog
|
<SuggestibleInput onValueChange={() => {}} suggestions={['a', 'b', 'c']} />
|
||||||
isOpen
|
|
||||||
onClose={() => null}
|
|
||||||
onSubmit={() => null}
|
|
||||||
onChange={() => null}
|
|
||||||
lookupName={'test'}
|
|
||||||
lookupTier={'test'}
|
|
||||||
lookupVersion={'test'}
|
|
||||||
lookupSpec={'test'}
|
|
||||||
isEdit={false}
|
|
||||||
allLookupTiers={['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
const { container } = render(lookupEditDialog, { container: document.body });
|
|
||||||
|
const { container } = render(suggestibleInput);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(container.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
HTMLInputProps,
|
||||||
|
InputGroup,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
Popover,
|
||||||
|
Position,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export interface SuggestionGroup {
|
||||||
|
group: string;
|
||||||
|
suggestions: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SuggestibleInputProps extends HTMLInputProps {
|
||||||
|
onValueChange: (newValue: string) => void;
|
||||||
|
suggestions?: (string | SuggestionGroup)[];
|
||||||
|
large?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SuggestibleInput extends React.PureComponent<SuggestibleInputProps> {
|
||||||
|
constructor(props: SuggestibleInputProps, context: any) {
|
||||||
|
super(props, context);
|
||||||
|
// this.state = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSuggestionsMenu() {
|
||||||
|
const { suggestions, onValueChange } = this.props;
|
||||||
|
if (!suggestions) return undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu>
|
||||||
|
{suggestions.map(suggestion => {
|
||||||
|
if (typeof suggestion === 'string') {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
key={suggestion}
|
||||||
|
text={suggestion}
|
||||||
|
onClick={() => onValueChange(suggestion)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<MenuItem key={suggestion.group} text={suggestion.group}>
|
||||||
|
{suggestion.suggestions.map(suggestion => (
|
||||||
|
<MenuItem
|
||||||
|
key={suggestion}
|
||||||
|
text={suggestion}
|
||||||
|
onClick={() => onValueChange(suggestion)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { className, value, defaultValue, onValueChange, large, ...rest } = this.props;
|
||||||
|
const suggestionsMenu = this.renderSuggestionsMenu();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InputGroup
|
||||||
|
className={classNames('suggestible-input', className)}
|
||||||
|
value={value as string}
|
||||||
|
defaultValue={defaultValue as string}
|
||||||
|
onChange={(e: any) => {
|
||||||
|
onValueChange(e.target.value);
|
||||||
|
}}
|
||||||
|
rightElement={
|
||||||
|
suggestionsMenu && (
|
||||||
|
<Popover content={suggestionsMenu} position={Position.BOTTOM_RIGHT} autoFocus={false}>
|
||||||
|
<Button icon={IconNames.CARET_DOWN} minimal />
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
large={large}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,17 +16,14 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Intent } from '@blueprintjs/core';
|
|
||||||
import { IconNames } from '@blueprintjs/icons';
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
import copy from 'copy-to-clipboard';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { AppToaster } from '../../singletons/toaster';
|
|
||||||
import { ActionIcon } from '../action-icon/action-icon';
|
import { ActionIcon } from '../action-icon/action-icon';
|
||||||
|
|
||||||
import './table-cell.scss';
|
import './table-cell.scss';
|
||||||
|
|
||||||
export interface NullTableCellProps extends React.Props<any> {
|
export interface NullTableCellProps {
|
||||||
value?: any;
|
value?: any;
|
||||||
timestamp?: boolean;
|
timestamp?: boolean;
|
||||||
unparseable?: boolean;
|
unparseable?: boolean;
|
||||||
|
|
|
@ -26,7 +26,7 @@ describe('table column', () => {
|
||||||
const tableColumn = (
|
const tableColumn = (
|
||||||
<TableColumnSelector
|
<TableColumnSelector
|
||||||
columns={['a', 'b', 'c']}
|
columns={['a', 'b', 'c']}
|
||||||
onChange={(column: string) => {}}
|
onChange={() => {}}
|
||||||
tableColumnsHidden={['a', 'b', 'c']}
|
tableColumnsHidden={['a', 'b', 'c']}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Button, Checkbox, FormGroup, Menu, Popover, Position } from '@blueprintjs/core';
|
import { Button, Menu, Popover, Position } from '@blueprintjs/core';
|
||||||
import { IconNames } from '@blueprintjs/icons';
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import { MenuCheckbox } from '../menu-checkbox/menu-checkbox';
|
||||||
|
|
||||||
import './table-column-selector.scss';
|
import './table-column-selector.scss';
|
||||||
|
|
||||||
interface TableColumnSelectorProps extends React.Props<any> {
|
interface TableColumnSelectorProps {
|
||||||
columns: string[];
|
columns: string[];
|
||||||
onChange: (column: string) => void;
|
onChange: (column: string) => void;
|
||||||
tableColumnsHidden: string[];
|
tableColumnsHidden: string[];
|
||||||
|
|
|
@ -18,5 +18,4 @@
|
||||||
|
|
||||||
.timed-button {
|
.timed-button {
|
||||||
padding: 10px 10px 5px 10px;
|
padding: 10px 10px 5px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,9 @@ import {
|
||||||
|
|
||||||
import './console-application.scss';
|
import './console-application.scss';
|
||||||
|
|
||||||
export interface ConsoleApplicationProps extends React.Props<any> {
|
type Capabilities = 'working-with-sql' | 'working-without-sql' | 'broken';
|
||||||
|
|
||||||
|
export interface ConsoleApplicationProps {
|
||||||
hideLegacy: boolean;
|
hideLegacy: boolean;
|
||||||
baseURL?: string;
|
baseURL?: string;
|
||||||
customHeaderName?: string;
|
customHeaderName?: string;
|
||||||
|
@ -60,11 +62,9 @@ export class ConsoleApplication extends React.PureComponent<
|
||||||
> {
|
> {
|
||||||
static MESSAGE_KEY = 'druid-console-message';
|
static MESSAGE_KEY = 'druid-console-message';
|
||||||
static MESSAGE_DISMISSED = 'dismissed';
|
static MESSAGE_DISMISSED = 'dismissed';
|
||||||
private capabilitiesQueryManager: QueryManager<string, string>;
|
private capabilitiesQueryManager: QueryManager<string, Capabilities>;
|
||||||
|
|
||||||
static async discoverCapabilities(): Promise<
|
static async discoverCapabilities(): Promise<Capabilities> {
|
||||||
'working-with-sql' | 'working-without-sql' | 'broken'
|
|
||||||
> {
|
|
||||||
try {
|
try {
|
||||||
await axios.post('/druid/v2/sql', { query: 'SELECT 1337' });
|
await axios.post('/druid/v2/sql', { query: 'SELECT 1337' });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -109,13 +109,13 @@ export class ConsoleApplication extends React.PureComponent<
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private supervisorId: string | null;
|
private supervisorId: string | undefined;
|
||||||
private taskId: string | null;
|
private taskId: string | undefined;
|
||||||
private openDialog: string | null;
|
private openDialog: string | undefined;
|
||||||
private datasource: string | null;
|
private datasource: string | undefined;
|
||||||
private onlyUnavailable: boolean | null;
|
private onlyUnavailable: boolean | undefined;
|
||||||
private initQuery: string | null;
|
private initQuery: string | undefined;
|
||||||
private middleManager: string | null;
|
private middleManager: string | undefined;
|
||||||
|
|
||||||
constructor(props: ConsoleApplicationProps, context: any) {
|
constructor(props: ConsoleApplicationProps, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
@ -134,16 +134,16 @@ export class ConsoleApplication extends React.PureComponent<
|
||||||
}
|
}
|
||||||
|
|
||||||
this.capabilitiesQueryManager = new QueryManager({
|
this.capabilitiesQueryManager = new QueryManager({
|
||||||
processQuery: async (query: string) => {
|
processQuery: async () => {
|
||||||
const capabilities = await ConsoleApplication.discoverCapabilities();
|
const capabilities = await ConsoleApplication.discoverCapabilities();
|
||||||
if (capabilities !== 'working-with-sql') {
|
if (capabilities !== 'working-with-sql') {
|
||||||
ConsoleApplication.shownNotifications(capabilities);
|
ConsoleApplication.shownNotifications(capabilities);
|
||||||
}
|
}
|
||||||
return capabilities;
|
return capabilities;
|
||||||
},
|
},
|
||||||
onStateChange: ({ result, loading, error }) => {
|
onStateChange: ({ result, loading }) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
noSqlMode: result === 'working-with-sql' ? false : true,
|
noSqlMode: result !== 'working-with-sql',
|
||||||
capabilitiesLoading: loading,
|
capabilitiesLoading: loading,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -160,13 +160,13 @@ export class ConsoleApplication extends React.PureComponent<
|
||||||
|
|
||||||
private resetInitialsWithDelay() {
|
private resetInitialsWithDelay() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.taskId = null;
|
this.taskId = undefined;
|
||||||
this.supervisorId = null;
|
this.supervisorId = undefined;
|
||||||
this.openDialog = null;
|
this.openDialog = undefined;
|
||||||
this.datasource = null;
|
this.datasource = undefined;
|
||||||
this.onlyUnavailable = null;
|
this.onlyUnavailable = undefined;
|
||||||
this.initQuery = null;
|
this.initQuery = undefined;
|
||||||
this.middleManager = null;
|
this.middleManager = undefined;
|
||||||
}, 50);
|
}, 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +177,7 @@ export class ConsoleApplication extends React.PureComponent<
|
||||||
this.resetInitialsWithDelay();
|
this.resetInitialsWithDelay();
|
||||||
};
|
};
|
||||||
|
|
||||||
private goToTask = (taskId: string | null, openDialog?: string) => {
|
private goToTask = (taskId: string | undefined, openDialog?: string) => {
|
||||||
this.taskId = taskId;
|
this.taskId = taskId;
|
||||||
if (openDialog) this.openDialog = openDialog;
|
if (openDialog) this.openDialog = openDialog;
|
||||||
window.location.hash = 'tasks';
|
window.location.hash = 'tasks';
|
||||||
|
|
|
@ -24,7 +24,7 @@ import { AboutDialog } from './about-dialog';
|
||||||
describe('about dialog', () => {
|
describe('about dialog', () => {
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
const aboutDialog = <AboutDialog onClose={() => null} />;
|
const aboutDialog = <AboutDialog onClose={() => null} />;
|
||||||
const { container } = render(aboutDialog, { container: document.body });
|
render(aboutDialog);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(document.body.lastChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,7 +28,7 @@ import {
|
||||||
DRUID_WEBSITE,
|
DRUID_WEBSITE,
|
||||||
} from '../../variables';
|
} from '../../variables';
|
||||||
|
|
||||||
export interface AboutDialogProps extends React.Props<any> {
|
export interface AboutDialogProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,13 +28,13 @@ describe('async action dialog', () => {
|
||||||
action={() => {
|
action={() => {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}}
|
}}
|
||||||
onClose={(success: boolean) => null}
|
onClose={() => null}
|
||||||
confirmButtonText={'test'}
|
confirmButtonText={'test'}
|
||||||
successText={'test'}
|
successText={'test'}
|
||||||
failText={'test'}
|
failText={'test'}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const { container } = render(asyncActionDialog, { container: document.body });
|
render(asyncActionDialog);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(document.body.lastChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,25 +16,14 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { Button, Classes, Dialog, Icon, Intent, ProgressBar } from '@blueprintjs/core';
|
||||||
Button,
|
|
||||||
ButtonGroup,
|
|
||||||
Classes,
|
|
||||||
Dialog,
|
|
||||||
FormGroup,
|
|
||||||
Icon,
|
|
||||||
Intent,
|
|
||||||
NumericInput,
|
|
||||||
ProgressBar,
|
|
||||||
TagInput,
|
|
||||||
} from '@blueprintjs/core';
|
|
||||||
import { IconName } from '@blueprintjs/icons';
|
import { IconName } from '@blueprintjs/icons';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { AppToaster } from '../../singletons/toaster';
|
import { AppToaster } from '../../singletons/toaster';
|
||||||
|
|
||||||
export interface AsyncAlertDialogProps extends React.Props<any> {
|
export interface AsyncAlertDialogProps {
|
||||||
action: null | (() => Promise<void>);
|
action: null | (() => Promise<void>);
|
||||||
onClose: (success: boolean) => void;
|
onClose: (success: boolean) => void;
|
||||||
confirmButtonText: string;
|
confirmButtonText: string;
|
||||||
|
|
|
@ -240,7 +240,7 @@ exports[`compaction dialog matches snapshot 1`] = `
|
||||||
class="bp3-form-content"
|
class="bp3-form-content"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="bp3-input-group"
|
class="bp3-input-group suggestible-input"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
class="bp3-input"
|
class="bp3-input"
|
||||||
|
|
|
@ -26,13 +26,13 @@ describe('compaction dialog', () => {
|
||||||
const compactionDialog = (
|
const compactionDialog = (
|
||||||
<CompactionDialog
|
<CompactionDialog
|
||||||
onClose={() => null}
|
onClose={() => null}
|
||||||
onSave={(config: any) => null}
|
onSave={() => null}
|
||||||
onDelete={() => null}
|
onDelete={() => null}
|
||||||
datasource={'test'}
|
datasource={'test'}
|
||||||
configData={'test'}
|
configData={'test'}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const { container } = render(compactionDialog, { container: document.body });
|
render(compactionDialog);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(document.body.lastChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { AutoForm } from '../../components';
|
||||||
|
|
||||||
import './compaction-dialog.scss';
|
import './compaction-dialog.scss';
|
||||||
|
|
||||||
export interface CompactionDialogProps extends React.Props<any> {
|
export interface CompactionDialogProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSave: (config: any) => void;
|
onSave: (config: any) => void;
|
||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
|
|
|
@ -16,7 +16,7 @@ exports[`coordinator dynamic config matches snapshot 1`] = `
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="bp3-dialog snitch-dialog coordinator-dynamic-config"
|
class="bp3-dialog snitch-dialog coordinator-dynamic-config-dialog"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="bp3-dialog-header"
|
class="bp3-dialog-header"
|
||||||
|
@ -76,16 +76,6 @@ exports[`coordinator dynamic config matches snapshot 1`] = `
|
||||||
<div
|
<div
|
||||||
class="bp3-dialog-footer-actions"
|
class="bp3-dialog-footer-actions"
|
||||||
>
|
>
|
||||||
<button
|
|
||||||
class="bp3-button bp3-minimal left-align-button"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="bp3-button-text"
|
|
||||||
>
|
|
||||||
History
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
class="bp3-button bp3-intent-primary"
|
class="bp3-button bp3-intent-primary"
|
||||||
type="button"
|
type="button"
|
|
@ -16,7 +16,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.coordinator-dynamic-config {
|
.coordinator-dynamic-config-dialog {
|
||||||
&.bp3-dialog {
|
&.bp3-dialog {
|
||||||
margin-top: 5vh;
|
margin-top: 5vh;
|
||||||
top: 5%;
|
top: 5%;
|
|
@ -19,12 +19,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-testing-library';
|
import { render } from 'react-testing-library';
|
||||||
|
|
||||||
import { CoordinatorDynamicConfigDialog } from './coordinator-dynamic-config';
|
import { CoordinatorDynamicConfigDialog } from './coordinator-dynamic-config-dialog';
|
||||||
|
|
||||||
describe('coordinator dynamic config', () => {
|
describe('coordinator dynamic config', () => {
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
const coordinatorDynamicConfig = <CoordinatorDynamicConfigDialog onClose={() => null} />;
|
const coordinatorDynamicConfig = <CoordinatorDynamicConfigDialog onClose={() => null} />;
|
||||||
const { container } = render(coordinatorDynamicConfig, { container: document.body });
|
render(coordinatorDynamicConfig);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(document.body.lastChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -26,9 +26,9 @@ import { AppToaster } from '../../singletons/toaster';
|
||||||
import { getDruidErrorMessage, QueryManager } from '../../utils';
|
import { getDruidErrorMessage, QueryManager } from '../../utils';
|
||||||
import { SnitchDialog } from '../snitch-dialog/snitch-dialog';
|
import { SnitchDialog } from '../snitch-dialog/snitch-dialog';
|
||||||
|
|
||||||
import './coordinator-dynamic-config.scss';
|
import './coordinator-dynamic-config-dialog.scss';
|
||||||
|
|
||||||
export interface CoordinatorDynamicConfigDialogProps extends React.Props<any> {
|
export interface CoordinatorDynamicConfigDialogProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ export class CoordinatorDynamicConfigDialog extends React.PureComponent<
|
||||||
CoordinatorDynamicConfigDialogProps,
|
CoordinatorDynamicConfigDialogProps,
|
||||||
CoordinatorDynamicConfigDialogState
|
CoordinatorDynamicConfigDialogState
|
||||||
> {
|
> {
|
||||||
private historyQueryManager: QueryManager<string, any>;
|
private historyQueryManager: QueryManager<null, any>;
|
||||||
|
|
||||||
constructor(props: CoordinatorDynamicConfigDialogProps) {
|
constructor(props: CoordinatorDynamicConfigDialogProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -49,24 +49,24 @@ export class CoordinatorDynamicConfigDialog extends React.PureComponent<
|
||||||
dynamicConfig: null,
|
dynamicConfig: null,
|
||||||
historyRecords: [],
|
historyRecords: [],
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.getClusterConfig();
|
|
||||||
|
|
||||||
this.historyQueryManager = new QueryManager({
|
this.historyQueryManager = new QueryManager({
|
||||||
processQuery: async query => {
|
processQuery: async () => {
|
||||||
const historyResp = await axios(`/druid/coordinator/v1/config/history?count=100`);
|
const historyResp = await axios(`/druid/coordinator/v1/config/history?count=100`);
|
||||||
return historyResp.data;
|
return historyResp.data;
|
||||||
},
|
},
|
||||||
onStateChange: ({ result, loading, error }) => {
|
onStateChange: ({ result }) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
historyRecords: result,
|
historyRecords: result,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.historyQueryManager.runQuery(`dummy`);
|
componentDidMount() {
|
||||||
|
this.getClusterConfig();
|
||||||
|
|
||||||
|
this.historyQueryManager.runQuery(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getClusterConfig() {
|
async getClusterConfig() {
|
||||||
|
@ -118,7 +118,7 @@ export class CoordinatorDynamicConfigDialog extends React.PureComponent<
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SnitchDialog
|
<SnitchDialog
|
||||||
className="coordinator-dynamic-config"
|
className="coordinator-dynamic-config-dialog"
|
||||||
isOpen
|
isOpen
|
||||||
onSave={this.saveClusterConfig}
|
onSave={this.saveClusterConfig}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
|
@ -32,7 +32,7 @@ describe('history dialog', () => {
|
||||||
isOpen
|
isOpen
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const { container } = render(historyDialog, { container: document.body });
|
render(historyDialog);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(document.body.lastChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,7 +24,7 @@ import { JSONCollapse } from '../../components';
|
||||||
import './history-dialog.scss';
|
import './history-dialog.scss';
|
||||||
|
|
||||||
interface HistoryDialogProps extends IDialogProps {
|
interface HistoryDialogProps extends IDialogProps {
|
||||||
historyRecords: any;
|
historyRecords: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HistoryDialogState {}
|
interface HistoryDialogState {}
|
||||||
|
@ -45,13 +45,13 @@ export class HistoryDialog extends React.PureComponent<HistoryDialogProps, Histo
|
||||||
<>
|
<>
|
||||||
<span className="history-dialog-title">History</span>
|
<span className="history-dialog-title">History</span>
|
||||||
<div className="history-record-entries">
|
<div className="history-record-entries">
|
||||||
{historyRecords.map((record: any) => {
|
{historyRecords.map((record, i) => {
|
||||||
const auditInfo = record.auditInfo;
|
const auditInfo = record.auditInfo;
|
||||||
const auditTime = record.auditTime;
|
const auditTime = record.auditTime;
|
||||||
const formattedTime = auditTime.replace('T', ' ').substring(0, auditTime.length - 5);
|
const formattedTime = auditTime.replace('T', ' ').substring(0, auditTime.length - 5);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={record.auditTime} className="history-record-entry">
|
<div key={i} className="history-record-entry">
|
||||||
<Card>
|
<Card>
|
||||||
<div className="history-record-title">
|
<div className="history-record-title">
|
||||||
<span className="history-record-title-change">Change</span>
|
<span className="history-record-title-change">Change</span>
|
||||||
|
|
|
@ -18,10 +18,10 @@
|
||||||
export * from './about-dialog/about-dialog';
|
export * from './about-dialog/about-dialog';
|
||||||
export * from './async-action-dialog/async-action-dialog';
|
export * from './async-action-dialog/async-action-dialog';
|
||||||
export * from './compaction-dialog/compaction-dialog';
|
export * from './compaction-dialog/compaction-dialog';
|
||||||
export * from './coordinator-dynamic-config/coordinator-dynamic-config';
|
export * from './coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog';
|
||||||
export * from './history-dialog/history-dialog';
|
export * from './history-dialog/history-dialog';
|
||||||
export * from './lookup-edit-dialog/lookup-edit-dialog';
|
export * from './lookup-edit-dialog/lookup-edit-dialog';
|
||||||
export * from './overlord-dynamic-config/overlord-dynamic-config';
|
export * from './overlord-dynamic-config-dialog/overlord-dynamic-config-dialog';
|
||||||
export * from './query-plan-dialog/query-plan-dialog';
|
export * from './query-plan-dialog/query-plan-dialog';
|
||||||
export * from './retention-dialog/retention-dialog';
|
export * from './retention-dialog/retention-dialog';
|
||||||
export * from './snitch-dialog/snitch-dialog';
|
export * from './snitch-dialog/snitch-dialog';
|
||||||
|
|
|
@ -38,7 +38,7 @@ describe('lookup edit dialog', () => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const { container } = render(lookupEditDialog, { container: document.body });
|
render(lookupEditDialog);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(document.body.lastChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,7 +32,7 @@ import { validJson } from '../../utils';
|
||||||
|
|
||||||
import './lookup-edit-dialog.scss';
|
import './lookup-edit-dialog.scss';
|
||||||
|
|
||||||
export interface LookupEditDialogProps extends React.Props<any> {
|
export interface LookupEditDialogProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSubmit: () => void;
|
onSubmit: () => void;
|
||||||
|
@ -105,7 +105,6 @@ export class LookupEditDialog extends React.PureComponent<
|
||||||
lookupVersion,
|
lookupVersion,
|
||||||
onChange,
|
onChange,
|
||||||
isEdit,
|
isEdit,
|
||||||
allLookupTiers,
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const disableSubmit =
|
const disableSubmit =
|
||||||
|
@ -155,8 +154,6 @@ export class LookupEditDialog extends React.PureComponent<
|
||||||
value={lookupSpec}
|
value={lookupSpec}
|
||||||
editorProps={{ $blockScrolling: Infinity }}
|
editorProps={{ $blockScrolling: Infinity }}
|
||||||
setOptions={{
|
setOptions={{
|
||||||
enableBasicAutocompletion: false,
|
|
||||||
enableLiveAutocompletion: false,
|
|
||||||
tabSize: 2,
|
tabSize: 2,
|
||||||
}}
|
}}
|
||||||
style={{}}
|
style={{}}
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`overload dynamic config matches snapshot 1`] = `
|
||||||
|
<div
|
||||||
|
class="bp3-portal"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bp3-overlay bp3-overlay-open bp3-overlay-scroll-container"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bp3-overlay-backdrop bp3-overlay-appear bp3-overlay-appear-active"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="bp3-dialog-container bp3-overlay-content bp3-overlay-appear bp3-overlay-appear-active"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bp3-dialog snitch-dialog overlord-dynamic-config-dialog"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bp3-dialog-header"
|
||||||
|
>
|
||||||
|
<h4
|
||||||
|
class="bp3-heading"
|
||||||
|
>
|
||||||
|
Overlord dynamic config
|
||||||
|
</h4>
|
||||||
|
<button
|
||||||
|
aria-label="Close"
|
||||||
|
class="bp3-button bp3-minimal bp3-dialog-close-button"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="bp3-icon bp3-icon-small-cross"
|
||||||
|
icon="small-cross"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
data-icon="small-cross"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
width="20"
|
||||||
|
>
|
||||||
|
<desc>
|
||||||
|
small-cross
|
||||||
|
</desc>
|
||||||
|
<path
|
||||||
|
d="M11.41 10l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 0 0-1.71-.71L10 8.59l-3.29-3.3a1.003 1.003 0 0 0-1.42 1.42L8.59 10 5.3 13.29c-.19.18-.3.43-.3.71a1.003 1.003 0 0 0 1.71.71l3.29-3.3 3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 0 0 .71-1.71L11.41 10z"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="bp3-dialog-body"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Edit the overlord dynamic configuration on the fly. For more information please refer to the
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="https://druid.apache.org/docs/latest/configuration/index.html#overlord-dynamic-configuration"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
documentation
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="auto-form"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="bp3-dialog-footer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bp3-dialog-footer-actions"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="bp3-button bp3-intent-primary"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="bp3-button-text"
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="bp3-icon bp3-icon-arrow-right"
|
||||||
|
icon="arrow-right"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
data-icon="arrow-right"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
width="16"
|
||||||
|
>
|
||||||
|
<desc>
|
||||||
|
arrow-right
|
||||||
|
</desc>
|
||||||
|
<path
|
||||||
|
d="M14.7 7.29l-5-5a.965.965 0 0 0-.71-.3 1.003 1.003 0 0 0-.71 1.71l3.29 3.29H1.99c-.55 0-1 .45-1 1s.45 1 1 1h9.59l-3.29 3.29a1.003 1.003 0 0 0 1.42 1.42l5-5c.18-.18.29-.43.29-.71s-.12-.52-.3-.7z"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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 React from 'react';
|
||||||
|
import { render } from 'react-testing-library';
|
||||||
|
|
||||||
|
import { OverlordDynamicConfigDialog } from './overlord-dynamic-config-dialog';
|
||||||
|
|
||||||
|
describe('overload dynamic config', () => {
|
||||||
|
it('matches snapshot', () => {
|
||||||
|
const lookupEditDialog = <OverlordDynamicConfigDialog onClose={() => null} />;
|
||||||
|
|
||||||
|
render(lookupEditDialog);
|
||||||
|
expect(document.body.lastChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -16,7 +16,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.overlord-dynamic-config {
|
.overlord-dynamic-config-dialog {
|
||||||
&.bp3-dialog {
|
&.bp3-dialog {
|
||||||
margin-top: 5vh;
|
margin-top: 5vh;
|
||||||
top: 5%;
|
top: 5%;
|
|
@ -26,9 +26,9 @@ import { AppToaster } from '../../singletons/toaster';
|
||||||
import { getDruidErrorMessage, QueryManager } from '../../utils';
|
import { getDruidErrorMessage, QueryManager } from '../../utils';
|
||||||
import { SnitchDialog } from '../snitch-dialog/snitch-dialog';
|
import { SnitchDialog } from '../snitch-dialog/snitch-dialog';
|
||||||
|
|
||||||
import './overlord-dynamic-config.scss';
|
import './overlord-dynamic-config-dialog.scss';
|
||||||
|
|
||||||
export interface OverlordDynamicConfigDialogProps extends React.Props<any> {
|
export interface OverlordDynamicConfigDialogProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,22 +51,22 @@ export class OverlordDynamicConfigDialog extends React.PureComponent<
|
||||||
allJSONValid: true,
|
allJSONValid: true,
|
||||||
historyRecords: [],
|
historyRecords: [],
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.getConfig();
|
|
||||||
|
|
||||||
this.historyQueryManager = new QueryManager({
|
this.historyQueryManager = new QueryManager({
|
||||||
processQuery: async query => {
|
processQuery: async () => {
|
||||||
const historyResp = await axios(`/druid/indexer/v1/worker/history?count=100`);
|
const historyResp = await axios(`/druid/indexer/v1/worker/history?count=100`);
|
||||||
return historyResp.data;
|
return historyResp.data;
|
||||||
},
|
},
|
||||||
onStateChange: ({ result, loading, error }) => {
|
onStateChange: ({ result }) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
historyRecords: result,
|
historyRecords: result,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.getConfig();
|
||||||
|
|
||||||
this.historyQueryManager.runQuery(`dummy`);
|
this.historyQueryManager.runQuery(`dummy`);
|
||||||
}
|
}
|
||||||
|
@ -120,7 +120,7 @@ export class OverlordDynamicConfigDialog extends React.PureComponent<
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SnitchDialog
|
<SnitchDialog
|
||||||
className="overlord-dynamic-config"
|
className="overlord-dynamic-config-dialog"
|
||||||
isOpen
|
isOpen
|
||||||
onSave={this.saveConfig}
|
onSave={this.saveConfig}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
|
@ -1,356 +0,0 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`overload dynamic config matches snapshot 1`] = `
|
|
||||||
<div
|
|
||||||
class="bp3-portal"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="bp3-overlay bp3-overlay-open bp3-overlay-scroll-container"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="bp3-overlay-backdrop bp3-overlay-appear bp3-overlay-appear-active"
|
|
||||||
tabindex="0"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="bp3-dialog-container bp3-overlay-content bp3-overlay-appear bp3-overlay-appear-active"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="bp3-dialog lookup-edit-dialog"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="bp3-dialog-header"
|
|
||||||
>
|
|
||||||
<h4
|
|
||||||
class="bp3-heading"
|
|
||||||
>
|
|
||||||
Add lookup
|
|
||||||
</h4>
|
|
||||||
<button
|
|
||||||
aria-label="Close"
|
|
||||||
class="bp3-button bp3-minimal bp3-dialog-close-button"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="bp3-icon bp3-icon-small-cross"
|
|
||||||
icon="small-cross"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
data-icon="small-cross"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
width="20"
|
|
||||||
>
|
|
||||||
<desc>
|
|
||||||
small-cross
|
|
||||||
</desc>
|
|
||||||
<path
|
|
||||||
d="M11.41 10l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 0 0-1.71-.71L10 8.59l-3.29-3.3a1.003 1.003 0 0 0-1.42 1.42L8.59 10 5.3 13.29c-.19.18-.3.43-.3.71a1.003 1.003 0 0 0 1.71.71l3.29-3.3 3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 0 0 .71-1.71L11.41 10z"
|
|
||||||
fill-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="bp3-form-group lookup-label"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="bp3-label"
|
|
||||||
>
|
|
||||||
Name:
|
|
||||||
|
|
||||||
<span
|
|
||||||
class="bp3-text-muted"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
class="bp3-form-content"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="bp3-input-group"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
class="bp3-input"
|
|
||||||
placeholder="Enter the lookup name"
|
|
||||||
style="padding-right: 10px;"
|
|
||||||
type="text"
|
|
||||||
value="test"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="bp3-form-group lookup-label"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="bp3-label"
|
|
||||||
>
|
|
||||||
Tier:
|
|
||||||
|
|
||||||
<span
|
|
||||||
class="bp3-text-muted"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
class="bp3-form-content"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="bp3-html-select"
|
|
||||||
>
|
|
||||||
<select>
|
|
||||||
<option
|
|
||||||
value="a"
|
|
||||||
>
|
|
||||||
a
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
value="b"
|
|
||||||
>
|
|
||||||
b
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
value="c"
|
|
||||||
>
|
|
||||||
c
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
value="d"
|
|
||||||
>
|
|
||||||
d
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
value="e"
|
|
||||||
>
|
|
||||||
e
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
value="f"
|
|
||||||
>
|
|
||||||
f
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
value="g"
|
|
||||||
>
|
|
||||||
g
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
value="h"
|
|
||||||
>
|
|
||||||
h
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
value="i"
|
|
||||||
>
|
|
||||||
i
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
value="j"
|
|
||||||
>
|
|
||||||
j
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<span
|
|
||||||
class="bp3-icon bp3-icon-double-caret-vertical"
|
|
||||||
icon="double-caret-vertical"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
data-icon="double-caret-vertical"
|
|
||||||
height="16"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
width="16"
|
|
||||||
>
|
|
||||||
<desc>
|
|
||||||
double-caret-vertical
|
|
||||||
</desc>
|
|
||||||
<path
|
|
||||||
d="M5 7h6a1.003 1.003 0 0 0 .71-1.71l-3-3C8.53 2.11 8.28 2 8 2s-.53.11-.71.29l-3 3A1.003 1.003 0 0 0 5 7zm6 2H5a1.003 1.003 0 0 0-.71 1.71l3 3c.18.18.43.29.71.29s.53-.11.71-.29l3-3A1.003 1.003 0 0 0 11 9z"
|
|
||||||
fill-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="bp3-form-group lookup-label"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="bp3-label"
|
|
||||||
>
|
|
||||||
Version:
|
|
||||||
|
|
||||||
<span
|
|
||||||
class="bp3-text-muted"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
class="bp3-form-content"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="bp3-input-group"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
class="bp3-input"
|
|
||||||
placeholder="Enter the lookup version"
|
|
||||||
style="padding-right: 0px;"
|
|
||||||
type="text"
|
|
||||||
value="test"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
class="bp3-input-action"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="bp3-button bp3-minimal"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="bp3-button-text"
|
|
||||||
>
|
|
||||||
Use ISO as version
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="bp3-form-group lookup-label"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="bp3-label"
|
|
||||||
>
|
|
||||||
Spec:
|
|
||||||
|
|
||||||
<span
|
|
||||||
class="bp3-text-muted"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
class="bp3-form-content"
|
|
||||||
/>
|
|
||||||
</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;"
|
|
||||||
>
|
|
||||||
<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
|
|
||||||
class="bp3-dialog-footer"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="bp3-dialog-footer-actions"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="bp3-button"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="bp3-button-text"
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="bp3-button bp3-disabled bp3-intent-primary"
|
|
||||||
disabled=""
|
|
||||||
tabindex="-1"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="bp3-button-text"
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
|
@ -30,7 +30,7 @@ describe('query plan dialog', () => {
|
||||||
onClose={() => null}
|
onClose={() => null}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const { container } = render(queryPlanDialog, { container: document.body });
|
render(queryPlanDialog);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(document.body.lastChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { BasicQueryExplanation, SemiJoinQueryExplanation } from '../../utils';
|
||||||
|
|
||||||
import './query-plan-dialog.scss';
|
import './query-plan-dialog.scss';
|
||||||
|
|
||||||
export interface QueryPlanDialogProps extends React.Props<any> {
|
export interface QueryPlanDialogProps {
|
||||||
explainResult: BasicQueryExplanation | SemiJoinQueryExplanation | string | null;
|
explainResult: BasicQueryExplanation | SemiJoinQueryExplanation | string | null;
|
||||||
explainError: Error | null;
|
explainError: Error | null;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
|
|
@ -116,16 +116,6 @@ exports[`retention dialog matches snapshot 1`] = `
|
||||||
<div
|
<div
|
||||||
class="bp3-dialog-footer-actions"
|
class="bp3-dialog-footer-actions"
|
||||||
>
|
>
|
||||||
<button
|
|
||||||
class="bp3-button bp3-minimal left-align-button"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="bp3-button-text"
|
|
||||||
>
|
|
||||||
History
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
class="bp3-button"
|
class="bp3-button"
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
@ -1,56 +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 React from 'react';
|
|
||||||
|
|
||||||
import { reorderArray } from './retention-dialog';
|
|
||||||
|
|
||||||
describe('reorderArray', () => {
|
|
||||||
it('works when nothing changes', () => {
|
|
||||||
const array = ['a', 'b', 'c', 'd', 'e'];
|
|
||||||
|
|
||||||
const newArray = reorderArray(array, 0, 0);
|
|
||||||
|
|
||||||
expect(newArray).toEqual(['a', 'b', 'c', 'd', 'e']);
|
|
||||||
expect(array).toEqual(['a', 'b', 'c', 'd', 'e']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('works upward', () => {
|
|
||||||
const array = ['a', 'b', 'c', 'd', 'e'];
|
|
||||||
|
|
||||||
let newArray = reorderArray(array, 2, 1);
|
|
||||||
expect(newArray).toEqual(['a', 'c', 'b', 'd', 'e']);
|
|
||||||
expect(array).toEqual(['a', 'b', 'c', 'd', 'e']);
|
|
||||||
|
|
||||||
newArray = reorderArray(array, 2, 0);
|
|
||||||
expect(newArray).toEqual(['c', 'a', 'b', 'd', 'e']);
|
|
||||||
expect(array).toEqual(['a', 'b', 'c', 'd', 'e']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('works downward', () => {
|
|
||||||
const array = ['a', 'b', 'c', 'd', 'e'];
|
|
||||||
|
|
||||||
let newArray = reorderArray(array, 2, 3);
|
|
||||||
expect(newArray).toEqual(['a', 'b', 'c', 'd', 'e']);
|
|
||||||
expect(array).toEqual(['a', 'b', 'c', 'd', 'e']);
|
|
||||||
|
|
||||||
newArray = reorderArray(array, 2, 4);
|
|
||||||
expect(newArray).toEqual(['a', 'b', 'd', 'c', 'e']);
|
|
||||||
expect(array).toEqual(['a', 'b', 'c', 'd', 'e']);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -19,7 +19,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-testing-library';
|
import { render } from 'react-testing-library';
|
||||||
|
|
||||||
import { RetentionDialog } from './retention-dialog';
|
import { reorderArray, RetentionDialog } from './retention-dialog';
|
||||||
|
|
||||||
describe('retention dialog', () => {
|
describe('retention dialog', () => {
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
|
@ -33,7 +33,42 @@ describe('retention dialog', () => {
|
||||||
onSave={() => null}
|
onSave={() => null}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const { container } = render(retentionDialog, { container: document.body });
|
render(retentionDialog);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(document.body.lastChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('reorderArray', () => {
|
||||||
|
it('works when nothing changes', () => {
|
||||||
|
const array = ['a', 'b', 'c', 'd', 'e'];
|
||||||
|
|
||||||
|
const newArray = reorderArray(array, 0, 0);
|
||||||
|
|
||||||
|
expect(newArray).toEqual(['a', 'b', 'c', 'd', 'e']);
|
||||||
|
expect(array).toEqual(['a', 'b', 'c', 'd', 'e']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works upward', () => {
|
||||||
|
const array = ['a', 'b', 'c', 'd', 'e'];
|
||||||
|
|
||||||
|
let newArray = reorderArray(array, 2, 1);
|
||||||
|
expect(newArray).toEqual(['a', 'c', 'b', 'd', 'e']);
|
||||||
|
expect(array).toEqual(['a', 'b', 'c', 'd', 'e']);
|
||||||
|
|
||||||
|
newArray = reorderArray(array, 2, 0);
|
||||||
|
expect(newArray).toEqual(['c', 'a', 'b', 'd', 'e']);
|
||||||
|
expect(array).toEqual(['a', 'b', 'c', 'd', 'e']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works downward', () => {
|
||||||
|
const array = ['a', 'b', 'c', 'd', 'e'];
|
||||||
|
|
||||||
|
let newArray = reorderArray(array, 2, 3);
|
||||||
|
expect(newArray).toEqual(['a', 'b', 'c', 'd', 'e']);
|
||||||
|
expect(array).toEqual(['a', 'b', 'c', 'd', 'e']);
|
||||||
|
|
||||||
|
newArray = reorderArray(array, 2, 4);
|
||||||
|
expect(newArray).toEqual(['a', 'b', 'd', 'c', 'e']);
|
||||||
|
expect(array).toEqual(['a', 'b', 'c', 'd', 'e']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { IconNames } from '@blueprintjs/icons';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { Rule, RuleEditor } from '../../components';
|
import { RuleEditor } from '../../components';
|
||||||
import { QueryManager } from '../../utils';
|
import { QueryManager } from '../../utils';
|
||||||
import { SnitchDialog } from '../snitch-dialog/snitch-dialog';
|
import { SnitchDialog } from '../snitch-dialog/snitch-dialog';
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ export function reorderArray<T>(items: T[], oldIndex: number, newIndex: number):
|
||||||
return newItems;
|
return newItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RetentionDialogProps extends React.Props<any> {
|
export interface RetentionDialogProps {
|
||||||
datasource: string;
|
datasource: string;
|
||||||
rules: any[];
|
rules: any[];
|
||||||
tiers: string[];
|
tiers: string[];
|
||||||
|
@ -64,23 +64,23 @@ export class RetentionDialog extends React.PureComponent<
|
||||||
currentRules: props.rules,
|
currentRules: props.rules,
|
||||||
historyRecords: [],
|
historyRecords: [],
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const { datasource } = this.props;
|
|
||||||
this.historyQueryManager = new QueryManager({
|
this.historyQueryManager = new QueryManager({
|
||||||
processQuery: async query => {
|
processQuery: async datasource => {
|
||||||
const historyResp = await axios(`/druid/coordinator/v1/rules/${datasource}/history`);
|
const historyResp = await axios(`/druid/coordinator/v1/rules/${datasource}/history`);
|
||||||
return historyResp.data;
|
return historyResp.data;
|
||||||
},
|
},
|
||||||
onStateChange: ({ result, loading, error }) => {
|
onStateChange: ({ result }) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
historyRecords: result,
|
historyRecords: result,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.historyQueryManager.runQuery(`dummy`);
|
componentDidMount() {
|
||||||
|
const { datasource } = this.props;
|
||||||
|
this.historyQueryManager.runQuery(datasource);
|
||||||
}
|
}
|
||||||
|
|
||||||
private save = (comment: string) => {
|
private save = (comment: string) => {
|
||||||
|
@ -106,7 +106,7 @@ export class RetentionDialog extends React.PureComponent<
|
||||||
onDeleteRule = (index: number) => {
|
onDeleteRule = (index: number) => {
|
||||||
const { currentRules } = this.state;
|
const { currentRules } = this.state;
|
||||||
|
|
||||||
const newRules = (currentRules || []).filter((r, i) => i !== index);
|
const newRules = (currentRules || []).filter((_r, i) => i !== index);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
currentRules: newRules,
|
currentRules: newRules,
|
||||||
|
|
|
@ -148,7 +148,18 @@ exports[`task table action dialog matches snapshot 1`] = `
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="footer-actions-left"
|
class="footer-actions-left"
|
||||||
/>
|
>
|
||||||
|
<button
|
||||||
|
class="bp3-button"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="bp3-button-text"
|
||||||
|
>
|
||||||
|
test
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="bp3-dialog-footer-actions"
|
class="bp3-dialog-footer-actions"
|
||||||
>
|
>
|
||||||
|
|
|
@ -21,19 +21,18 @@ import { render } from 'react-testing-library';
|
||||||
|
|
||||||
import { SegmentTableActionDialog } from './segment-table-action-dialog';
|
import { SegmentTableActionDialog } from './segment-table-action-dialog';
|
||||||
|
|
||||||
const basicAction = { title: 'test', onAction: () => null };
|
|
||||||
describe('task table action dialog', () => {
|
describe('task table action dialog', () => {
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
const taskTableActionDialog = (
|
const taskTableActionDialog = (
|
||||||
<SegmentTableActionDialog
|
<SegmentTableActionDialog
|
||||||
dataSourceId="test"
|
dataSourceId="test"
|
||||||
segmentId="test"
|
segmentId="test"
|
||||||
actions={[]}
|
actions={[{ title: 'test', onAction: () => null }]}
|
||||||
onClose={() => null}
|
onClose={() => null}
|
||||||
isOpen
|
isOpen
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const { container } = render(taskTableActionDialog, { container: document.body });
|
render(taskTableActionDialog);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(document.body.lastChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IDialogProps, TextArea } from '@blueprintjs/core';
|
import { IDialogProps } from '@blueprintjs/core';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { ShowJson } from '../../components';
|
import { ShowJson } from '../../components';
|
||||||
|
|
|
@ -24,7 +24,7 @@ exports[`clipboard dialog matches snapshot 1`] = `
|
||||||
<h4
|
<h4
|
||||||
class="bp3-heading"
|
class="bp3-heading"
|
||||||
>
|
>
|
||||||
Show value
|
Full value
|
||||||
</h4>
|
</h4>
|
||||||
<button
|
<button
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
|
|
|
@ -16,18 +16,17 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.show-value-dialog{
|
.show-value-dialog {
|
||||||
&.bp3-dialog{
|
&.bp3-dialog {
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bp3-input{
|
.bp3-input {
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
height: 400px;
|
height: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bp3-dialog-footer-actions{
|
.bp3-dialog-footer-actions {
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ describe('clipboard dialog', () => {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const { container } = render(compactionDialog, { container: document.body });
|
render(compactionDialog);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(document.body.lastChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,16 +15,16 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { Button, Classes, Dialog, IconName, Intent, TextArea } from '@blueprintjs/core';
|
import { Button, Classes, Dialog, Intent, TextArea } from '@blueprintjs/core';
|
||||||
import { IconNames } from '@blueprintjs/icons';
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
import copy = require('copy-to-clipboard');
|
import copy from 'copy-to-clipboard';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { AppToaster } from '../../singletons/toaster';
|
import { AppToaster } from '../../singletons/toaster';
|
||||||
|
|
||||||
import './show-value-dialog.scss';
|
import './show-value-dialog.scss';
|
||||||
|
|
||||||
export interface ShowValueDialogProps extends React.Props<any> {
|
export interface ShowValueDialogProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
str: string;
|
str: string;
|
||||||
}
|
}
|
||||||
|
@ -39,23 +39,22 @@ export class ShowValueDialog extends React.PureComponent<ShowValueDialogProps> {
|
||||||
const { onClose, str } = this.props;
|
const { onClose, str } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog className="show-value-dialog" isOpen onClose={onClose} title={'Show value'}>
|
<Dialog className="show-value-dialog" isOpen onClose={onClose} title="Full value">
|
||||||
<TextArea value={str} />
|
<TextArea value={str} />
|
||||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
<Button
|
<Button icon={IconNames.DUPLICATE} text={'Copy'} onClick={this.handleCopy} />
|
||||||
icon={IconNames.DUPLICATE}
|
<Button text={'Close'} intent={Intent.PRIMARY} onClick={onClose} />
|
||||||
text={'Copy'}
|
</div>
|
||||||
onClick={() => {
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleCopy = () => {
|
||||||
|
const { str } = this.props;
|
||||||
copy(str, { format: 'text/plain' });
|
copy(str, { format: 'text/plain' });
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: 'Value copied to clipboard',
|
message: 'Value copied to clipboard',
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
}}
|
};
|
||||||
/>
|
|
||||||
<Button text={'Close'} intent={'primary'} onClick={onClose} />
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ import { SnitchDialog } from './snitch-dialog';
|
||||||
describe('snitch dialog', () => {
|
describe('snitch dialog', () => {
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
const snitchDialog = <SnitchDialog onSave={() => null} isOpen />;
|
const snitchDialog = <SnitchDialog onSave={() => null} isOpen />;
|
||||||
const { container } = render(snitchDialog, { container: document.body });
|
render(snitchDialog);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(document.body.lastChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -44,7 +44,6 @@ export interface SnitchDialogState {
|
||||||
comment: string;
|
comment: string;
|
||||||
|
|
||||||
showFinalStep?: boolean;
|
showFinalStep?: boolean;
|
||||||
saveDisabled?: boolean;
|
|
||||||
|
|
||||||
showHistory?: boolean;
|
showHistory?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -55,7 +54,6 @@ export class SnitchDialog extends React.PureComponent<SnitchDialogProps, SnitchD
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
comment: '',
|
comment: '',
|
||||||
saveDisabled: true,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,11 +66,8 @@ export class SnitchDialog extends React.PureComponent<SnitchDialogProps, SnitchD
|
||||||
};
|
};
|
||||||
|
|
||||||
changeComment(newComment: string) {
|
changeComment(newComment: string) {
|
||||||
const { comment } = this.state;
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
comment: newComment,
|
comment: newComment,
|
||||||
saveDisabled: !newComment,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,15 +90,14 @@ export class SnitchDialog extends React.PureComponent<SnitchDialogProps, SnitchD
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
goToHistory = () => {
|
handleGoToHistory = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
showHistory: true,
|
showHistory: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
renderFinalStep() {
|
renderFinalStep() {
|
||||||
const { onClose, children } = this.props;
|
const { comment } = this.state;
|
||||||
const { saveDisabled, comment } = this.state;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog {...this.props}>
|
<Dialog {...this.props}>
|
||||||
|
@ -117,14 +111,14 @@ export class SnitchDialog extends React.PureComponent<SnitchDialogProps, SnitchD
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={Classes.DIALOG_FOOTER}>{this.renderActions(!comment)}</div>
|
||||||
<div className={Classes.DIALOG_FOOTER}>{this.renderActions(saveDisabled)}</div>
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderHistoryDialog() {
|
renderHistoryDialog() {
|
||||||
const { historyRecords } = this.props;
|
const { historyRecords } = this.props;
|
||||||
|
if (!historyRecords) return;
|
||||||
return (
|
return (
|
||||||
<HistoryDialog {...this.props} className="history-dialog" historyRecords={historyRecords}>
|
<HistoryDialog {...this.props} className="history-dialog" historyRecords={historyRecords}>
|
||||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
|
@ -142,8 +136,13 @@ export class SnitchDialog extends React.PureComponent<SnitchDialogProps, SnitchD
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
{showFinalStep || historyRecords === undefined ? null : (
|
{!showFinalStep && historyRecords && (
|
||||||
<Button className="left-align-button" minimal text="History" onClick={this.goToHistory} />
|
<Button
|
||||||
|
className="left-align-button"
|
||||||
|
minimal
|
||||||
|
text="History"
|
||||||
|
onClick={this.handleGoToHistory}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showFinalStep ? (
|
{showFinalStep ? (
|
||||||
|
@ -178,7 +177,7 @@ export class SnitchDialog extends React.PureComponent<SnitchDialogProps, SnitchD
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { onClose, className, children, saveDisabled } = this.props;
|
const { children, saveDisabled } = this.props;
|
||||||
const { showFinalStep, showHistory } = this.state;
|
const { showFinalStep, showHistory } = this.state;
|
||||||
|
|
||||||
if (showFinalStep) return this.renderFinalStep();
|
if (showFinalStep) return this.renderFinalStep();
|
||||||
|
@ -189,7 +188,6 @@ export class SnitchDialog extends React.PureComponent<SnitchDialogProps, SnitchD
|
||||||
return (
|
return (
|
||||||
<Dialog isOpen {...propsClone}>
|
<Dialog isOpen {...propsClone}>
|
||||||
<div className={Classes.DIALOG_BODY}>{children}</div>
|
<div className={Classes.DIALOG_BODY}>{children}</div>
|
||||||
|
|
||||||
<div className={Classes.DIALOG_FOOTER}>{this.renderActions(saveDisabled)}</div>
|
<div className={Classes.DIALOG_FOOTER}>{this.renderActions(saveDisabled)}</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|
|
@ -23,10 +23,8 @@ import { SpecDialog } from './spec-dialog';
|
||||||
|
|
||||||
describe('spec dialog', () => {
|
describe('spec dialog', () => {
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
const specDialog = (
|
const specDialog = <SpecDialog onSubmit={() => null} onClose={() => null} title={'test'} />;
|
||||||
<SpecDialog onSubmit={(spec: JSON) => null} onClose={() => null} title={'test'} />
|
render(specDialog);
|
||||||
);
|
expect(document.body.lastChild).toMatchSnapshot();
|
||||||
const { container } = render(specDialog, { container: document.body });
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,7 +22,7 @@ import AceEditor from 'react-ace';
|
||||||
|
|
||||||
import './spec-dialog.scss';
|
import './spec-dialog.scss';
|
||||||
|
|
||||||
export interface SpecDialogProps extends React.Props<any> {
|
export interface SpecDialogProps {
|
||||||
onSubmit: (spec: JSON) => void;
|
onSubmit: (spec: JSON) => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -84,10 +84,7 @@ export class SpecDialog extends React.PureComponent<SpecDialogProps, SpecDialogS
|
||||||
value={spec}
|
value={spec}
|
||||||
width="100%"
|
width="100%"
|
||||||
setOptions={{
|
setOptions={{
|
||||||
enableBasicAutocompletion: true,
|
|
||||||
enableLiveAutocompletion: true,
|
|
||||||
showLineNumbers: true,
|
showLineNumbers: true,
|
||||||
enableSnippets: true,
|
|
||||||
tabSize: 2,
|
tabSize: 2,
|
||||||
}}
|
}}
|
||||||
style={{}}
|
style={{}}
|
||||||
|
|
|
@ -32,7 +32,7 @@ describe('supervisor table action dialog', () => {
|
||||||
isOpen
|
isOpen
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const { container } = render(supervisorTableActionDialog, { container: document.body });
|
render(supervisorTableActionDialog);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(document.body.lastChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -75,7 +75,7 @@ $side-bar-width: 120px;
|
||||||
left: $side-bar-width;
|
left: $side-bar-width;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px 15px 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ describe('table action dialog', () => {
|
||||||
isOpen
|
isOpen
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const { container } = render(tableActionDialog, { container: document.body });
|
render(tableActionDialog);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(document.body.lastChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -47,11 +47,11 @@ export class TableActionDialog extends React.PureComponent<TableActionDialogProp
|
||||||
<Dialog className="table-action-dialog" isOpen={isOpen} onClose={onClose} title={title}>
|
<Dialog className="table-action-dialog" isOpen={isOpen} onClose={onClose} title={title}>
|
||||||
<div className={Classes.DIALOG_BODY}>
|
<div className={Classes.DIALOG_BODY}>
|
||||||
<div className="side-bar">
|
<div className="side-bar">
|
||||||
{sideButtonMetadata.map((d: SideButtonMetaData) => (
|
{sideButtonMetadata.map((d, i) => (
|
||||||
<Button
|
<Button
|
||||||
className="tab-button"
|
className="tab-button"
|
||||||
icon={<Icon icon={d.icon} iconSize={20} />}
|
icon={<Icon icon={d.icon} iconSize={20} />}
|
||||||
key={d.text}
|
key={i}
|
||||||
text={d.text}
|
text={d.text}
|
||||||
intent={d.active ? Intent.PRIMARY : Intent.NONE}
|
intent={d.active ? Intent.PRIMARY : Intent.NONE}
|
||||||
minimal={!d.active}
|
minimal={!d.active}
|
||||||
|
|
|
@ -33,7 +33,7 @@ describe('task table action dialog', () => {
|
||||||
isOpen
|
isOpen
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const { container } = render(taskTableActionDialog, { container: document.body });
|
render(taskTableActionDialog);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(document.body.lastChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Button, HTMLSelect, InputGroup, Intent } from '@blueprintjs/core';
|
import { Button, HTMLSelect, InputGroup } from '@blueprintjs/core';
|
||||||
import { IconNames } from '@blueprintjs/icons';
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
import FileSaver from 'file-saver';
|
import FileSaver from 'file-saver';
|
||||||
import hasOwnProp from 'has-own-prop';
|
import hasOwnProp from 'has-own-prop';
|
||||||
|
@ -232,11 +232,6 @@ export function pluralIfNeeded(n: number, singular: string, plural?: string): st
|
||||||
return `${formatNumber(n)} ${n === 1 ? singular : plural}`;
|
return `${formatNumber(n)} ${n === 1 ? singular : plural}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getHeadProp(results: Record<string, any>[], prop: string): any {
|
|
||||||
if (!results || !results.length) return null;
|
|
||||||
return results[0][prop] || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------
|
// ----------------------------
|
||||||
|
|
||||||
export function parseJson(json: string): any {
|
export function parseJson(json: string): any {
|
||||||
|
@ -274,10 +269,6 @@ export function parseStringToJSON(s: string): JSON | null {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectDefined<T, Q>(xs: (Q | null | undefined)[]): Q[] {
|
|
||||||
return xs.filter(Boolean) as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function filterMap<T, Q>(xs: T[], f: (x: T, i?: number) => Q | null | undefined): Q[] {
|
export function filterMap<T, Q>(xs: T[], f: (x: T, i?: number) => Q | null | undefined): Q[] {
|
||||||
return (xs.map(f) as any).filter(Boolean);
|
return (xs.map(f) as any).filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Code } from '@blueprintjs/core';
|
import { Code } from '@blueprintjs/core';
|
||||||
import { number } from 'prop-types';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { Field } from '../components/auto-form/auto-form';
|
import { Field } from '../components/auto-form/auto-form';
|
||||||
|
@ -184,11 +183,12 @@ export interface ParseSpec {
|
||||||
|
|
||||||
export function hasParallelAbility(spec: IngestionSpec): boolean {
|
export function hasParallelAbility(spec: IngestionSpec): boolean {
|
||||||
const specType = getSpecType(spec);
|
const specType = getSpecType(spec);
|
||||||
return spec.type === 'index' || spec.type === 'index_parallel';
|
return specType === 'index' || specType === 'index_parallel';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isParallel(spec: IngestionSpec): boolean {
|
export function isParallel(spec: IngestionSpec): boolean {
|
||||||
return spec.type === 'index_parallel';
|
const specType = getSpecType(spec);
|
||||||
|
return specType === 'index_parallel';
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DimensionMode = 'specific' | 'auto-detect';
|
export type DimensionMode = 'specific' | 'auto-detect';
|
||||||
|
@ -1482,6 +1482,45 @@ const TUNING_CONFIG_FORM_FIELDS: Field<TuningConfig>[] = [
|
||||||
placeholder: 'Default: 1/6 of max JVM memory',
|
placeholder: 'Default: 1/6 of max JVM memory',
|
||||||
info: <>Used in determining when intermediate persists to disk should occur.</>,
|
info: <>Used in determining when intermediate persists to disk should occur.</>,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'indexSpec.bitmap.type',
|
||||||
|
label: 'Index bitmap type',
|
||||||
|
type: 'string',
|
||||||
|
defaultValue: 'concise',
|
||||||
|
suggestions: ['concise', 'roaring'],
|
||||||
|
info: <>Compression format for bitmap indexes.</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'indexSpec.dimensionCompression',
|
||||||
|
label: 'Index dimension compression',
|
||||||
|
type: 'string',
|
||||||
|
defaultValue: 'lz4',
|
||||||
|
suggestions: ['lz4', 'lzf', 'uncompressed'],
|
||||||
|
info: <>Compression format for dimension columns.</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'indexSpec.metricCompression',
|
||||||
|
label: 'Index metric compression',
|
||||||
|
type: 'string',
|
||||||
|
defaultValue: 'lz4',
|
||||||
|
suggestions: ['lz4', 'lzf', 'uncompressed'],
|
||||||
|
info: <>Compression format for metric columns.</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'indexSpec.longEncoding',
|
||||||
|
label: 'Index long encoding',
|
||||||
|
type: 'string',
|
||||||
|
defaultValue: 'longs',
|
||||||
|
suggestions: ['longs', 'auto'],
|
||||||
|
info: (
|
||||||
|
<>
|
||||||
|
Encoding format for long-typed columns. Applies regardless of whether they are dimensions or
|
||||||
|
metrics. <Code>auto</Code> encodes the values using offset or lookup table depending on
|
||||||
|
column cardinality, and store them with variable size. <Code>longs</Code> stores the value
|
||||||
|
as-is with 8 bytes each.
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'intermediatePersistPeriod',
|
name: 'intermediatePersistPeriod',
|
||||||
type: 'duration',
|
type: 'duration',
|
||||||
|
@ -1513,10 +1552,6 @@ const TUNING_CONFIG_FORM_FIELDS: Field<TuningConfig>[] = [
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'forceExtendableShardSpecs',
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'pushTimeout',
|
name: 'pushTimeout',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
|
|
@ -25,24 +25,21 @@ export class LocalStorageBackedArray<T> {
|
||||||
constructor(key: LocalStorageKeys, array?: T[]) {
|
constructor(key: LocalStorageKeys, array?: T[]) {
|
||||||
this.key = key;
|
this.key = key;
|
||||||
if (!Array.isArray(array)) {
|
if (!Array.isArray(array)) {
|
||||||
this.getDataFromStorage();
|
this.storedArray = this.getDataFromStorage();
|
||||||
} else {
|
} else {
|
||||||
this.storedArray = array;
|
this.storedArray = array;
|
||||||
this.setDataInStorage();
|
this.setDataInStorage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDataFromStorage(): void {
|
private getDataFromStorage(): T[] {
|
||||||
let possibleArray: any;
|
|
||||||
try {
|
try {
|
||||||
possibleArray = JSON.parse(String(localStorageGet(this.key)));
|
const possibleArray: any = JSON.parse(String(localStorageGet(this.key)));
|
||||||
|
if (!Array.isArray(possibleArray)) return [];
|
||||||
|
return possibleArray;
|
||||||
} catch {
|
} catch {
|
||||||
// show all columns by default
|
return [];
|
||||||
possibleArray = [];
|
|
||||||
}
|
}
|
||||||
if (!Array.isArray(possibleArray)) possibleArray = [];
|
|
||||||
|
|
||||||
this.storedArray = possibleArray;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private setDataInStorage(): void {
|
private setDataInStorage(): void {
|
||||||
|
|
|
@ -32,6 +32,7 @@ export const LocalStorageKeys = {
|
||||||
SEGMENTS_REFRESH_RATE: 'segments-refresh-rate' as 'segments-refresh-rate',
|
SEGMENTS_REFRESH_RATE: 'segments-refresh-rate' as 'segments-refresh-rate',
|
||||||
SERVERS_REFRESH_RATE: 'servers-refresh-rate' as 'servers-refresh-rate',
|
SERVERS_REFRESH_RATE: 'servers-refresh-rate' as 'servers-refresh-rate',
|
||||||
SUPERVISORS_REFRESH_RATE: 'supervisors-refresh-rate' as 'supervisors-refresh-rate',
|
SUPERVISORS_REFRESH_RATE: 'supervisors-refresh-rate' as 'supervisors-refresh-rate',
|
||||||
|
LOOKUPS_REFRESH_RATE: 'lookups-refresh-rate' as 'lookups-refresh-rate',
|
||||||
};
|
};
|
||||||
export type LocalStorageKeys = typeof LocalStorageKeys[keyof typeof LocalStorageKeys];
|
export type LocalStorageKeys = typeof LocalStorageKeys[keyof typeof LocalStorageKeys];
|
||||||
|
|
||||||
|
|
|
@ -36,8 +36,8 @@ export class QueryManager<Q, R> {
|
||||||
private onStateChange?: (queryResolve: QueryStateInt<R>) => void;
|
private onStateChange?: (queryResolve: QueryStateInt<R>) => void;
|
||||||
|
|
||||||
private terminated = false;
|
private terminated = false;
|
||||||
private nextQuery: Q;
|
private nextQuery: Q | undefined;
|
||||||
private lastQuery: Q;
|
private lastQuery: Q | undefined;
|
||||||
private actuallyLoading = false;
|
private actuallyLoading = false;
|
||||||
private state: QueryStateInt<R> = {
|
private state: QueryStateInt<R> = {
|
||||||
result: null,
|
result: null,
|
||||||
|
@ -73,6 +73,7 @@ export class QueryManager<Q, R> {
|
||||||
|
|
||||||
private run() {
|
private run() {
|
||||||
this.lastQuery = this.nextQuery;
|
this.lastQuery = this.nextQuery;
|
||||||
|
if (typeof this.lastQuery === 'undefined') return;
|
||||||
this.currentQueryId++;
|
this.currentQueryId++;
|
||||||
const myQueryId = this.currentQueryId;
|
const myQueryId = this.currentQueryId;
|
||||||
|
|
||||||
|
@ -134,7 +135,7 @@ export class QueryManager<Q, R> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLastQuery(): Q {
|
public getLastQuery(): Q | undefined {
|
||||||
return this.lastQuery;
|
return this.lastQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ exports[`data source view matches snapshot 1`] = `
|
||||||
Array [
|
Array [
|
||||||
"Datasource",
|
"Datasource",
|
||||||
"Availability",
|
"Availability",
|
||||||
|
"Segment load/drop",
|
||||||
"Retention",
|
"Retention",
|
||||||
"Compaction",
|
"Compaction",
|
||||||
"Size",
|
"Size",
|
||||||
|
@ -110,6 +111,14 @@ exports[`data source view matches snapshot 1`] = `
|
||||||
"show": true,
|
"show": true,
|
||||||
"sortMethod": [Function],
|
"sortMethod": [Function],
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"Cell": [Function],
|
||||||
|
"Header": "Segment load/drop",
|
||||||
|
"accessor": "num_segments_to_load",
|
||||||
|
"filterable": false,
|
||||||
|
"id": "load-drop",
|
||||||
|
"show": true,
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"Cell": [Function],
|
"Cell": [Function],
|
||||||
"Header": "Retention",
|
"Header": "Retention",
|
||||||
|
|
|
@ -24,11 +24,7 @@ import { DatasourcesView } from './datasource-view';
|
||||||
describe('data source view', () => {
|
describe('data source view', () => {
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
const dataSourceView = shallow(
|
const dataSourceView = shallow(
|
||||||
<DatasourcesView
|
<DatasourcesView goToQuery={() => {}} goToSegments={() => {}} noSqlMode={false} />,
|
||||||
goToQuery={(initSql: string) => {}}
|
|
||||||
goToSegments={(datasource: string, onlyUnavailable?: boolean) => {}}
|
|
||||||
noSqlMode={false}
|
|
||||||
/>,
|
|
||||||
);
|
);
|
||||||
expect(dataSourceView).toMatchSnapshot();
|
expect(dataSourceView).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,16 +16,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { Button, FormGroup, InputGroup, Intent, Switch } from '@blueprintjs/core';
|
||||||
Button,
|
|
||||||
FormGroup,
|
|
||||||
Icon,
|
|
||||||
InputGroup,
|
|
||||||
Intent,
|
|
||||||
Popover,
|
|
||||||
Position,
|
|
||||||
Switch,
|
|
||||||
} from '@blueprintjs/core';
|
|
||||||
import { IconNames } from '@blueprintjs/icons';
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
@ -55,12 +46,14 @@ import {
|
||||||
} from '../../utils';
|
} from '../../utils';
|
||||||
import { BasicAction } from '../../utils/basic-action';
|
import { BasicAction } from '../../utils/basic-action';
|
||||||
import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array';
|
import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array';
|
||||||
|
import { deepGet } from '../../utils/object-change';
|
||||||
|
|
||||||
import './datasource-view.scss';
|
import './datasource-view.scss';
|
||||||
|
|
||||||
const tableColumns: string[] = [
|
const tableColumns: string[] = [
|
||||||
'Datasource',
|
'Datasource',
|
||||||
'Availability',
|
'Availability',
|
||||||
|
'Segment load/drop',
|
||||||
'Retention',
|
'Retention',
|
||||||
'Compaction',
|
'Compaction',
|
||||||
'Size',
|
'Size',
|
||||||
|
@ -70,13 +63,25 @@ const tableColumns: string[] = [
|
||||||
const tableColumnsNoSql: string[] = [
|
const tableColumnsNoSql: string[] = [
|
||||||
'Datasource',
|
'Datasource',
|
||||||
'Availability',
|
'Availability',
|
||||||
|
'Segment load/drop',
|
||||||
'Retention',
|
'Retention',
|
||||||
'Compaction',
|
'Compaction',
|
||||||
'Size',
|
'Size',
|
||||||
ActionCell.COLUMN_LABEL,
|
ActionCell.COLUMN_LABEL,
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface DatasourcesViewProps extends React.Props<any> {
|
function formatLoadDrop(segmentsToLoad: number, segmentsToDrop: number): string {
|
||||||
|
const loadDrop: string[] = [];
|
||||||
|
if (segmentsToLoad) {
|
||||||
|
loadDrop.push(`${segmentsToLoad} segments to load`);
|
||||||
|
}
|
||||||
|
if (segmentsToDrop) {
|
||||||
|
loadDrop.push(`${segmentsToDrop} segments to drop`);
|
||||||
|
}
|
||||||
|
return loadDrop.join(', ') || 'No segments to load/drop';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DatasourcesViewProps {
|
||||||
goToQuery: (initSql: string) => void;
|
goToQuery: (initSql: string) => void;
|
||||||
goToSegments: (datasource: string, onlyUnavailable?: boolean) => void;
|
goToSegments: (datasource: string, onlyUnavailable?: boolean) => void;
|
||||||
noSqlMode: boolean;
|
noSqlMode: boolean;
|
||||||
|
@ -90,10 +95,12 @@ interface Datasource {
|
||||||
|
|
||||||
interface DatasourceQueryResultRow {
|
interface DatasourceQueryResultRow {
|
||||||
datasource: string;
|
datasource: string;
|
||||||
num_available_segments: number;
|
|
||||||
num_rows: number;
|
|
||||||
num_segments: number;
|
num_segments: number;
|
||||||
|
num_available_segments: number;
|
||||||
|
num_segments_to_load: number;
|
||||||
|
num_segments_to_drop: number;
|
||||||
size: number;
|
size: number;
|
||||||
|
num_rows: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatasourcesViewState {
|
export interface DatasourcesViewState {
|
||||||
|
@ -124,6 +131,17 @@ export class DatasourcesView extends React.PureComponent<
|
||||||
static FULLY_AVAILABLE_COLOR = '#57d500';
|
static FULLY_AVAILABLE_COLOR = '#57d500';
|
||||||
static PARTIALLY_AVAILABLE_COLOR = '#ffbf00';
|
static PARTIALLY_AVAILABLE_COLOR = '#ffbf00';
|
||||||
|
|
||||||
|
static DATASOURCE_SQL = `SELECT
|
||||||
|
datasource,
|
||||||
|
COUNT(*) FILTER (WHERE (is_published = 1 AND is_overshadowed = 0) OR is_realtime = 1) AS num_segments,
|
||||||
|
COUNT(*) FILTER (WHERE is_available = 1 AND ((is_published = 1 AND is_overshadowed = 0) OR is_realtime = 1)) AS num_available_segments,
|
||||||
|
COUNT(*) FILTER (WHERE is_published = 1 AND is_overshadowed = 0 AND is_available = 0) AS num_segments_to_load,
|
||||||
|
COUNT(*) FILTER (WHERE is_available = 1 AND NOT ((is_published = 1 AND is_overshadowed = 0) OR is_realtime = 1)) AS num_segments_to_drop,
|
||||||
|
SUM("size") FILTER (WHERE (is_published = 1 AND is_overshadowed = 0) OR is_realtime = 1) AS size,
|
||||||
|
SUM("num_rows") FILTER (WHERE (is_published = 1 AND is_overshadowed = 0) OR is_realtime = 1) AS num_rows
|
||||||
|
FROM sys.segments
|
||||||
|
GROUP BY 1`;
|
||||||
|
|
||||||
static formatRules(rules: any[]): string {
|
static formatRules(rules: any[]): string {
|
||||||
if (rules.length === 0) {
|
if (rules.length === 0) {
|
||||||
return 'No rules';
|
return 'No rules';
|
||||||
|
@ -135,7 +153,7 @@ export class DatasourcesView extends React.PureComponent<
|
||||||
}
|
}
|
||||||
|
|
||||||
private datasourceQueryManager: QueryManager<
|
private datasourceQueryManager: QueryManager<
|
||||||
string,
|
boolean,
|
||||||
{ tiers: string[]; defaultRules: any[]; datasources: Datasource[] }
|
{ tiers: string[]; defaultRules: any[]; datasources: Datasource[] }
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
@ -162,30 +180,31 @@ export class DatasourcesView extends React.PureComponent<
|
||||||
LocalStorageKeys.DATASOURCE_TABLE_COLUMN_SELECTION,
|
LocalStorageKeys.DATASOURCE_TABLE_COLUMN_SELECTION,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount(): void {
|
|
||||||
const { noSqlMode } = this.props;
|
|
||||||
const { hiddenColumns } = this.state;
|
|
||||||
|
|
||||||
this.datasourceQueryManager = new QueryManager({
|
this.datasourceQueryManager = new QueryManager({
|
||||||
processQuery: async (query: string) => {
|
processQuery: async noSqlMode => {
|
||||||
let datasources: DatasourceQueryResultRow[];
|
let datasources: DatasourceQueryResultRow[];
|
||||||
if (!noSqlMode) {
|
if (!noSqlMode) {
|
||||||
datasources = await queryDruidSql({ query });
|
datasources = await queryDruidSql({ query: DatasourcesView.DATASOURCE_SQL });
|
||||||
} else {
|
} else {
|
||||||
const datasourcesResp = await axios.get('/druid/coordinator/v1/datasources?simple');
|
const datasourcesResp = await axios.get('/druid/coordinator/v1/datasources?simple');
|
||||||
const loadstatusResp = await axios.get('/druid/coordinator/v1/loadstatus?simple');
|
const loadstatusResp = await axios.get('/druid/coordinator/v1/loadstatus?simple');
|
||||||
const loadstatus = loadstatusResp.data;
|
const loadstatus = loadstatusResp.data;
|
||||||
datasources = datasourcesResp.data.map((d: any) => {
|
datasources = datasourcesResp.data.map(
|
||||||
|
(d: any): DatasourceQueryResultRow => {
|
||||||
|
const segmentsToLoad = Number(loadstatus[d.name] || 0);
|
||||||
|
const availableSegments = Number(deepGet(d, 'properties.segments.count'));
|
||||||
return {
|
return {
|
||||||
datasource: d.name,
|
datasource: d.name,
|
||||||
num_available_segments: d.properties.segments.count,
|
num_available_segments: availableSegments,
|
||||||
|
num_segments: availableSegments + segmentsToLoad,
|
||||||
|
num_segments_to_load: segmentsToLoad,
|
||||||
|
num_segments_to_drop: 0,
|
||||||
size: d.properties.segments.size,
|
size: d.properties.segments.size,
|
||||||
num_segments: d.properties.segments.count + loadstatus[d.name],
|
|
||||||
num_rows: -1,
|
num_rows: -1,
|
||||||
};
|
};
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const seen = countBy(datasources, (x: any) => x.datasource);
|
const seen = countBy(datasources, (x: any) => x.datasource);
|
||||||
|
@ -234,15 +253,11 @@ export class DatasourcesView extends React.PureComponent<
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.datasourceQueryManager.runQuery(`SELECT
|
componentDidMount(): void {
|
||||||
datasource,
|
const { noSqlMode } = this.props;
|
||||||
COUNT(*) AS num_segments,
|
this.datasourceQueryManager.runQuery(noSqlMode);
|
||||||
SUM(is_available) AS num_available_segments,
|
|
||||||
SUM("size") AS size,
|
|
||||||
SUM("num_rows") AS num_rows
|
|
||||||
FROM sys.segments
|
|
||||||
GROUP BY 1`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
componentWillUnmount(): void {
|
||||||
|
@ -581,7 +596,7 @@ GROUP BY 1`);
|
||||||
}
|
}
|
||||||
filterable
|
filterable
|
||||||
filtered={datasourcesFilter}
|
filtered={datasourcesFilter}
|
||||||
onFilteredChange={(filtered, column) => {
|
onFilteredChange={filtered => {
|
||||||
this.setState({ datasourcesFilter: filtered });
|
this.setState({ datasourcesFilter: filtered });
|
||||||
}}
|
}}
|
||||||
columns={[
|
columns={[
|
||||||
|
@ -669,6 +684,17 @@ GROUP BY 1`);
|
||||||
},
|
},
|
||||||
show: hiddenColumns.exists('Availability'),
|
show: hiddenColumns.exists('Availability'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Header: 'Segment load/drop',
|
||||||
|
id: 'load-drop',
|
||||||
|
accessor: 'num_segments_to_load',
|
||||||
|
filterable: false,
|
||||||
|
Cell: row => {
|
||||||
|
const { num_segments_to_load, num_segments_to_drop } = row.original;
|
||||||
|
return formatLoadDrop(num_segments_to_load, num_segments_to_drop);
|
||||||
|
},
|
||||||
|
show: hiddenColumns.exists('Segment load/drop'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Header: 'Retention',
|
Header: 'Retention',
|
||||||
id: 'retention',
|
id: 'retention',
|
||||||
|
@ -789,7 +815,7 @@ GROUP BY 1`);
|
||||||
<Button
|
<Button
|
||||||
icon={IconNames.APPLICATION}
|
icon={IconNames.APPLICATION}
|
||||||
text="Go to SQL"
|
text="Go to SQL"
|
||||||
onClick={() => goToQuery(this.datasourceQueryManager.getLastQuery())}
|
onClick={() => goToQuery(DatasourcesView.DATASOURCE_SQL)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Switch
|
<Switch
|
||||||
|
|
|
@ -19,10 +19,12 @@
|
||||||
import { Card, H5, Icon } from '@blueprintjs/core';
|
import { Card, H5, Icon } from '@blueprintjs/core';
|
||||||
import { IconName, IconNames } from '@blueprintjs/icons';
|
import { IconName, IconNames } from '@blueprintjs/icons';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { sum } from 'd3-array';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { UrlBaser } from '../../singletons/url-baser';
|
import { UrlBaser } from '../../singletons/url-baser';
|
||||||
import { getHeadProp, lookupBy, pluralIfNeeded, queryDruidSql, QueryManager } from '../../utils';
|
import { lookupBy, pluralIfNeeded, queryDruidSql, QueryManager } from '../../utils';
|
||||||
|
import { deepGet } from '../../utils/object-change';
|
||||||
|
|
||||||
import './home-view.scss';
|
import './home-view.scss';
|
||||||
|
|
||||||
|
@ -35,7 +37,7 @@ export interface CardOptions {
|
||||||
error?: string | null;
|
error?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HomeViewProps extends React.Props<any> {
|
export interface HomeViewProps {
|
||||||
noSqlMode: boolean;
|
noSqlMode: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +52,7 @@ export interface HomeViewState {
|
||||||
|
|
||||||
segmentCountLoading: boolean;
|
segmentCountLoading: boolean;
|
||||||
segmentCount: number;
|
segmentCount: number;
|
||||||
|
unavailableSegmentCount: number;
|
||||||
segmentCountError: string | null;
|
segmentCountError: string | null;
|
||||||
|
|
||||||
supervisorCountLoading: boolean;
|
supervisorCountLoading: boolean;
|
||||||
|
@ -77,12 +80,12 @@ export interface HomeViewState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HomeView extends React.PureComponent<HomeViewProps, HomeViewState> {
|
export class HomeView extends React.PureComponent<HomeViewProps, HomeViewState> {
|
||||||
private statusQueryManager: QueryManager<string, any>;
|
private statusQueryManager: QueryManager<null, any>;
|
||||||
private datasourceQueryManager: QueryManager<string, any>;
|
private datasourceQueryManager: QueryManager<boolean, any>;
|
||||||
private segmentQueryManager: QueryManager<string, any>;
|
private segmentQueryManager: QueryManager<boolean, any>;
|
||||||
private supervisorQueryManager: QueryManager<string, any>;
|
private supervisorQueryManager: QueryManager<null, any>;
|
||||||
private taskQueryManager: QueryManager<string, any>;
|
private taskQueryManager: QueryManager<boolean, any>;
|
||||||
private serverQueryManager: QueryManager<string, any>;
|
private serverQueryManager: QueryManager<boolean, any>;
|
||||||
|
|
||||||
constructor(props: HomeViewProps, context: any) {
|
constructor(props: HomeViewProps, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
@ -97,6 +100,7 @@ export class HomeView extends React.PureComponent<HomeViewProps, HomeViewState>
|
||||||
|
|
||||||
segmentCountLoading: false,
|
segmentCountLoading: false,
|
||||||
segmentCount: 0,
|
segmentCount: 0,
|
||||||
|
unavailableSegmentCount: 0,
|
||||||
segmentCountError: null,
|
segmentCountError: null,
|
||||||
|
|
||||||
supervisorCountLoading: false,
|
supervisorCountLoading: false,
|
||||||
|
@ -122,13 +126,9 @@ export class HomeView extends React.PureComponent<HomeViewProps, HomeViewState>
|
||||||
peonCount: 0,
|
peonCount: 0,
|
||||||
serverCountError: null,
|
serverCountError: null,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount(): void {
|
|
||||||
const { noSqlMode } = this.props;
|
|
||||||
|
|
||||||
this.statusQueryManager = new QueryManager({
|
this.statusQueryManager = new QueryManager({
|
||||||
processQuery: async query => {
|
processQuery: async () => {
|
||||||
const statusResp = await axios.get('/status');
|
const statusResp = await axios.get('/status');
|
||||||
return statusResp.data;
|
return statusResp.data;
|
||||||
},
|
},
|
||||||
|
@ -141,15 +141,13 @@ export class HomeView extends React.PureComponent<HomeViewProps, HomeViewState>
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.statusQueryManager.runQuery(`dummy`);
|
|
||||||
|
|
||||||
// -------------------------
|
|
||||||
|
|
||||||
this.datasourceQueryManager = new QueryManager({
|
this.datasourceQueryManager = new QueryManager({
|
||||||
processQuery: async query => {
|
processQuery: async noSqlMode => {
|
||||||
let datasources: string[];
|
let datasources: string[];
|
||||||
if (!noSqlMode) {
|
if (!noSqlMode) {
|
||||||
datasources = await queryDruidSql({ query });
|
datasources = await queryDruidSql({
|
||||||
|
query: `SELECT datasource FROM sys.segments GROUP BY 1`,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const datasourcesResp = await axios.get('/druid/coordinator/v1/datasources');
|
const datasourcesResp = await axios.get('/druid/coordinator/v1/datasources');
|
||||||
datasources = datasourcesResp.data;
|
datasources = datasourcesResp.data;
|
||||||
|
@ -165,45 +163,45 @@ export class HomeView extends React.PureComponent<HomeViewProps, HomeViewState>
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.datasourceQueryManager.runQuery(`SELECT datasource FROM sys.segments GROUP BY 1`);
|
|
||||||
|
|
||||||
// -------------------------
|
|
||||||
|
|
||||||
this.segmentQueryManager = new QueryManager({
|
this.segmentQueryManager = new QueryManager({
|
||||||
processQuery: async query => {
|
processQuery: async noSqlMode => {
|
||||||
if (noSqlMode) {
|
if (noSqlMode) {
|
||||||
const loadstatusResp = await axios.get('/druid/coordinator/v1/loadstatus?simple');
|
const loadstatusResp = await axios.get('/druid/coordinator/v1/loadstatus?simple');
|
||||||
const loadstatus = loadstatusResp.data;
|
const loadstatus = loadstatusResp.data;
|
||||||
const unavailableSegmentNum = Object.keys(loadstatus).reduce((sum, key) => {
|
const unavailableSegmentNum = sum(Object.keys(loadstatus), key => loadstatus[key]);
|
||||||
return sum + loadstatus[key];
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
const datasourcesMetaResp = await axios.get('/druid/coordinator/v1/datasources?simple');
|
const datasourcesMetaResp = await axios.get('/druid/coordinator/v1/datasources?simple');
|
||||||
const datasourcesMeta = datasourcesMetaResp.data;
|
const datasourcesMeta = datasourcesMetaResp.data;
|
||||||
const availableSegmentNum = datasourcesMeta.reduce((sum: number, curr: any) => {
|
const availableSegmentNum = sum(datasourcesMeta, (curr: any) =>
|
||||||
return sum + curr.properties.segments.count;
|
deepGet(curr, 'properties.segments.count'),
|
||||||
}, 0);
|
);
|
||||||
|
|
||||||
return availableSegmentNum + unavailableSegmentNum;
|
return {
|
||||||
|
count: availableSegmentNum + unavailableSegmentNum,
|
||||||
|
unavailable: unavailableSegmentNum,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
const segments = await queryDruidSql({ query });
|
const segments = await queryDruidSql({
|
||||||
return getHeadProp(segments, 'count') || 0;
|
query: `SELECT
|
||||||
|
COUNT(*) as "count",
|
||||||
|
COUNT(*) FILTER (WHERE is_available = 0) as "unavailable"
|
||||||
|
FROM sys.segments`,
|
||||||
|
});
|
||||||
|
return segments.length === 1 ? segments[0] : null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onStateChange: ({ result, loading, error }) => {
|
onStateChange: ({ result, loading, error }) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
segmentCountLoading: loading,
|
segmentCountLoading: loading,
|
||||||
segmentCount: result,
|
segmentCount: result ? result.count : 0,
|
||||||
|
unavailableSegmentCount: result ? result.unavailable : 0,
|
||||||
segmentCountError: error,
|
segmentCountError: error,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.segmentQueryManager.runQuery(`SELECT COUNT(*) as "count" FROM sys.segments`);
|
|
||||||
|
|
||||||
// -------------------------
|
|
||||||
this.supervisorQueryManager = new QueryManager({
|
this.supervisorQueryManager = new QueryManager({
|
||||||
processQuery: async (query: string) => {
|
processQuery: async () => {
|
||||||
const resp = await axios.get('/druid/indexer/v1/supervisor?full');
|
const resp = await axios.get('/druid/indexer/v1/supervisor?full');
|
||||||
const data = resp.data;
|
const data = resp.data;
|
||||||
const runningSupervisorCount = data.filter((d: any) => d.spec.suspended === false).length;
|
const runningSupervisorCount = data.filter((d: any) => d.spec.suspended === false).length;
|
||||||
|
@ -223,12 +221,8 @@ export class HomeView extends React.PureComponent<HomeViewProps, HomeViewState>
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.supervisorQueryManager.runQuery('dummy');
|
|
||||||
|
|
||||||
// -------------------------
|
|
||||||
|
|
||||||
this.taskQueryManager = new QueryManager({
|
this.taskQueryManager = new QueryManager({
|
||||||
processQuery: async query => {
|
processQuery: async noSqlMode => {
|
||||||
if (noSqlMode) {
|
if (noSqlMode) {
|
||||||
const completeTasksResp = await axios.get('/druid/indexer/v1/completeTasks');
|
const completeTasksResp = await axios.get('/druid/indexer/v1/completeTasks');
|
||||||
const runningTasksResp = await axios.get('/druid/indexer/v1/runningTasks');
|
const runningTasksResp = await axios.get('/druid/indexer/v1/runningTasks');
|
||||||
|
@ -243,7 +237,11 @@ export class HomeView extends React.PureComponent<HomeViewProps, HomeViewState>
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const taskCountsFromQuery: { status: string; count: number }[] = await queryDruidSql({
|
const taskCountsFromQuery: { status: string; count: number }[] = await queryDruidSql({
|
||||||
query,
|
query: `SELECT
|
||||||
|
CASE WHEN "status" = 'RUNNING' THEN "runner_status" ELSE "status" END AS "status",
|
||||||
|
COUNT (*) AS "count"
|
||||||
|
FROM sys.tasks
|
||||||
|
GROUP BY 1`,
|
||||||
});
|
});
|
||||||
return lookupBy(taskCountsFromQuery, x => x.status, x => x.count);
|
return lookupBy(taskCountsFromQuery, x => x.status, x => x.count);
|
||||||
}
|
}
|
||||||
|
@ -261,16 +259,8 @@ export class HomeView extends React.PureComponent<HomeViewProps, HomeViewState>
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.taskQueryManager.runQuery(`SELECT
|
|
||||||
CASE WHEN "status" = 'RUNNING' THEN "runner_status" ELSE "status" END AS "status",
|
|
||||||
COUNT (*) AS "count"
|
|
||||||
FROM sys.tasks
|
|
||||||
GROUP BY 1`);
|
|
||||||
|
|
||||||
// -------------------------
|
|
||||||
|
|
||||||
this.serverQueryManager = new QueryManager({
|
this.serverQueryManager = new QueryManager({
|
||||||
processQuery: async query => {
|
processQuery: async noSqlMode => {
|
||||||
if (noSqlMode) {
|
if (noSqlMode) {
|
||||||
const serversResp = await axios.get('/druid/coordinator/v1/servers?simple');
|
const serversResp = await axios.get('/druid/coordinator/v1/servers?simple');
|
||||||
const middleManagerResp = await axios.get('/druid/indexer/v1/workers');
|
const middleManagerResp = await axios.get('/druid/indexer/v1/workers');
|
||||||
|
@ -283,7 +273,9 @@ GROUP BY 1`);
|
||||||
const serverCountsFromQuery: {
|
const serverCountsFromQuery: {
|
||||||
server_type: string;
|
server_type: string;
|
||||||
count: number;
|
count: number;
|
||||||
}[] = await queryDruidSql({ query });
|
}[] = await queryDruidSql({
|
||||||
|
query: `SELECT server_type, COUNT(*) as "count" FROM sys.servers GROUP BY 1`,
|
||||||
|
});
|
||||||
return lookupBy(serverCountsFromQuery, x => x.server_type, x => x.count);
|
return lookupBy(serverCountsFromQuery, x => x.server_type, x => x.count);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -301,10 +293,17 @@ GROUP BY 1`);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.serverQueryManager.runQuery(
|
componentDidMount(): void {
|
||||||
`SELECT server_type, COUNT(*) as "count" FROM sys.servers GROUP BY 1`,
|
const { noSqlMode } = this.props;
|
||||||
);
|
|
||||||
|
this.statusQueryManager.runQuery(null);
|
||||||
|
this.datasourceQueryManager.runQuery(noSqlMode);
|
||||||
|
this.segmentQueryManager.runQuery(noSqlMode);
|
||||||
|
this.supervisorQueryManager.runQuery(null);
|
||||||
|
this.taskQueryManager.runQuery(noSqlMode);
|
||||||
|
this.serverQueryManager.runQuery(noSqlMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
componentWillUnmount(): void {
|
||||||
|
@ -362,7 +361,14 @@ GROUP BY 1`);
|
||||||
icon: IconNames.STACKED_CHART,
|
icon: IconNames.STACKED_CHART,
|
||||||
title: 'Segments',
|
title: 'Segments',
|
||||||
loading: state.segmentCountLoading,
|
loading: state.segmentCountLoading,
|
||||||
content: pluralIfNeeded(state.segmentCount, 'segment'),
|
content: (
|
||||||
|
<>
|
||||||
|
<p>{pluralIfNeeded(state.segmentCount, 'segment')}</p>
|
||||||
|
{Boolean(state.unavailableSegmentCount) && (
|
||||||
|
<p>{pluralIfNeeded(state.unavailableSegmentCount, 'unavailable segment')}</p>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
),
|
||||||
error: state.datasourceCountError,
|
error: state.datasourceCountError,
|
||||||
})}
|
})}
|
||||||
{this.renderCard({
|
{this.renderCard({
|
||||||
|
|
|
@ -153,7 +153,7 @@ exports[`load data view matches snapshot 1`] = `
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="main"
|
className="main bp3-input"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className="control"
|
className="control"
|
||||||
|
|
|
@ -22,12 +22,12 @@ import ReactTable from 'react-table';
|
||||||
|
|
||||||
import { TableCell } from '../../../components';
|
import { TableCell } from '../../../components';
|
||||||
import { caseInsensitiveContains, filterMap } from '../../../utils';
|
import { caseInsensitiveContains, filterMap } from '../../../utils';
|
||||||
import { DruidFilter, Transform } from '../../../utils/ingestion-spec';
|
import { DruidFilter } from '../../../utils/ingestion-spec';
|
||||||
import { HeaderAndRows } from '../../../utils/sampler';
|
import { HeaderAndRows, SampleEntry } from '../../../utils/sampler';
|
||||||
|
|
||||||
import './filter-table.scss';
|
import './filter-table.scss';
|
||||||
|
|
||||||
export interface FilterTableProps extends React.Props<any> {
|
export interface FilterTableProps {
|
||||||
sampleData: HeaderAndRows;
|
sampleData: HeaderAndRows;
|
||||||
columnFilter: string;
|
columnFilter: string;
|
||||||
dimensionFilters: DruidFilter[];
|
dimensionFilters: DruidFilter[];
|
||||||
|
@ -82,7 +82,7 @@ export class FilterTable extends React.PureComponent<FilterTableProps> {
|
||||||
headerClassName: columnClassName,
|
headerClassName: columnClassName,
|
||||||
className: columnClassName,
|
className: columnClassName,
|
||||||
id: String(i),
|
id: String(i),
|
||||||
accessor: row => (row.parsed ? row.parsed[columnName] : null),
|
accessor: (row: SampleEntry) => (row.parsed ? row.parsed[columnName] : null),
|
||||||
Cell: row => <TableCell value={row.value} timestamp={timestamp} />,
|
Cell: row => <TableCell value={row.value} timestamp={timestamp} />,
|
||||||
};
|
};
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
.load-data-view {
|
.load-data-view {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 10px 5px;
|
grid-gap: 15px 5px;
|
||||||
grid-template-columns: 1fr 280px;
|
grid-template-columns: 1fr 280px;
|
||||||
grid-template-rows: 55px 1fr 28px;
|
grid-template-rows: 55px 1fr 28px;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
|
@ -29,7 +29,8 @@
|
||||||
|
|
||||||
&.welcome {
|
&.welcome {
|
||||||
.main {
|
.main {
|
||||||
margin-left: -10px;
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
.bp3-card {
|
.bp3-card {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -37,8 +38,8 @@
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
width: 250px;
|
width: 250px;
|
||||||
height: 140px;
|
height: 140px;
|
||||||
margin-left: 15px;
|
margin-top: 10px;
|
||||||
margin-bottom: 15px;
|
margin-left: 10px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { LoadDataView } from './load-data-view';
|
||||||
|
|
||||||
describe('load data view', () => {
|
describe('load data view', () => {
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
const loadDataView = shallow(<LoadDataView goToTask={(taskId: string | null) => {}} />);
|
const loadDataView = shallow(<LoadDataView goToTask={() => {}} />);
|
||||||
expect(loadDataView).toMatchSnapshot();
|
expect(loadDataView).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,8 +25,6 @@ import {
|
||||||
Card,
|
Card,
|
||||||
Classes,
|
Classes,
|
||||||
Code,
|
Code,
|
||||||
Dialog,
|
|
||||||
Elevation,
|
|
||||||
FormGroup,
|
FormGroup,
|
||||||
H5,
|
H5,
|
||||||
HTMLSelect,
|
HTMLSelect,
|
||||||
|
@ -221,10 +219,10 @@ const VIEW_TITLE: Record<Step, string> = {
|
||||||
loading: 'Loading',
|
loading: 'Loading',
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface LoadDataViewProps extends React.Props<any> {
|
export interface LoadDataViewProps {
|
||||||
initSupervisorId?: string | null;
|
initSupervisorId?: string | null;
|
||||||
initTaskId?: string | null;
|
initTaskId?: string | null;
|
||||||
goToTask: (taskId: string | null, supervisor?: string) => void;
|
goToTask: (taskId: string | undefined, supervisor?: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoadDataViewState {
|
export interface LoadDataViewState {
|
||||||
|
@ -524,7 +522,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="main">
|
<div className="main bp3-input">
|
||||||
{this.renderIngestionCard('kafka')}
|
{this.renderIngestionCard('kafka')}
|
||||||
{this.renderIngestionCard('kinesis')}
|
{this.renderIngestionCard('kinesis')}
|
||||||
{this.renderIngestionCard('index:static-s3')}
|
{this.renderIngestionCard('index:static-s3')}
|
||||||
|
@ -679,7 +677,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
|
||||||
<Button
|
<Button
|
||||||
text="Submit task"
|
text="Submit task"
|
||||||
rightIcon={IconNames.ARROW_RIGHT}
|
rightIcon={IconNames.ARROW_RIGHT}
|
||||||
onClick={() => goToTask(null, 'task')}
|
onClick={() => goToTask(undefined, 'task')}
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
@ -695,7 +693,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
|
||||||
<Button
|
<Button
|
||||||
text="Submit supervisor"
|
text="Submit supervisor"
|
||||||
rightIcon={IconNames.ARROW_RIGHT}
|
rightIcon={IconNames.ARROW_RIGHT}
|
||||||
onClick={() => goToTask(null, 'supervisor')}
|
onClick={() => goToTask(undefined, 'supervisor')}
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
@ -703,7 +701,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
|
||||||
<Button
|
<Button
|
||||||
text="Submit task"
|
text="Submit task"
|
||||||
rightIcon={IconNames.ARROW_RIGHT}
|
rightIcon={IconNames.ARROW_RIGHT}
|
||||||
onClick={() => goToTask(null, 'task')}
|
onClick={() => goToTask(undefined, 'task')}
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
@ -1602,6 +1600,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sampleResponse.data.length) {
|
||||||
this.setState({
|
this.setState({
|
||||||
cacheKey: sampleResponse.cacheKey,
|
cacheKey: sampleResponse.cacheKey,
|
||||||
filterQueryState: new QueryState({
|
filterQueryState: new QueryState({
|
||||||
|
@ -1613,6 +1612,34 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The filters matched no data
|
||||||
|
let sampleResponseNoFilter: SampleResponse;
|
||||||
|
try {
|
||||||
|
const specNoFilter = deepSet(spec, 'dataSchema.transformSpec.filter', null);
|
||||||
|
sampleResponseNoFilter = await sampleForFilter(specNoFilter, sampleStrategy, cacheKey);
|
||||||
|
} catch (e) {
|
||||||
|
this.setState({
|
||||||
|
filterQueryState: new QueryState({ error: e.message }),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerAndRowsNoFilter = headerAndRowsFromSampleResponse(
|
||||||
|
sampleResponseNoFilter,
|
||||||
|
undefined,
|
||||||
|
['__time'].concat(parserColumns),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
cacheKey: sampleResponseNoFilter.cacheKey,
|
||||||
|
filterQueryState: new QueryState({
|
||||||
|
data: deepSet(headerAndRowsNoFilter, 'rows', []),
|
||||||
|
}),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getMemoizedDimensionFiltersFromSpec = memoize(spec => {
|
private getMemoizedDimensionFiltersFromSpec = memoize(spec => {
|
||||||
|
@ -2684,7 +2711,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
goToTask(null);
|
goToTask(undefined); // Can we get the supervisor ID here?
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import { HeaderAndRows, SampleEntry } from '../../../utils/sampler';
|
||||||
|
|
||||||
import './parse-data-table.scss';
|
import './parse-data-table.scss';
|
||||||
|
|
||||||
export interface ParseDataTableProps extends React.Props<any> {
|
export interface ParseDataTableProps {
|
||||||
sampleData: HeaderAndRows;
|
sampleData: HeaderAndRows;
|
||||||
columnFilter: string;
|
columnFilter: string;
|
||||||
canFlatten: boolean;
|
canFlatten: boolean;
|
||||||
|
|
|
@ -17,4 +17,5 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.parse-time-table {
|
.parse-time-table {
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ import { HeaderAndRows, SampleEntry } from '../../../utils/sampler';
|
||||||
|
|
||||||
import './parse-time-table.scss';
|
import './parse-time-table.scss';
|
||||||
|
|
||||||
export interface ParseTimeTableProps extends React.Props<any> {
|
export interface ParseTimeTableProps {
|
||||||
sampleBundle: {
|
sampleBundle: {
|
||||||
headerAndRows: HeaderAndRows;
|
headerAndRows: HeaderAndRows;
|
||||||
timestampSpec: TimestampSpec;
|
timestampSpec: TimestampSpec;
|
||||||
|
|
|
@ -21,12 +21,7 @@ import React from 'react';
|
||||||
import ReactTable from 'react-table';
|
import ReactTable from 'react-table';
|
||||||
|
|
||||||
import { TableCell } from '../../../components';
|
import { TableCell } from '../../../components';
|
||||||
import {
|
import { caseInsensitiveContains, filterMap, sortWithPrefixSuffix } from '../../../utils';
|
||||||
alphanumericCompare,
|
|
||||||
caseInsensitiveContains,
|
|
||||||
filterMap,
|
|
||||||
sortWithPrefixSuffix,
|
|
||||||
} from '../../../utils';
|
|
||||||
import {
|
import {
|
||||||
DimensionSpec,
|
DimensionSpec,
|
||||||
DimensionsSpec,
|
DimensionsSpec,
|
||||||
|
@ -35,13 +30,12 @@ import {
|
||||||
getMetricSpecName,
|
getMetricSpecName,
|
||||||
inflateDimensionSpec,
|
inflateDimensionSpec,
|
||||||
MetricSpec,
|
MetricSpec,
|
||||||
TimestampSpec,
|
|
||||||
} from '../../../utils/ingestion-spec';
|
} from '../../../utils/ingestion-spec';
|
||||||
import { HeaderAndRows, SampleEntry } from '../../../utils/sampler';
|
import { HeaderAndRows, SampleEntry } from '../../../utils/sampler';
|
||||||
|
|
||||||
import './schema-table.scss';
|
import './schema-table.scss';
|
||||||
|
|
||||||
export interface SchemaTableProps extends React.Props<any> {
|
export interface SchemaTableProps {
|
||||||
sampleBundle: {
|
sampleBundle: {
|
||||||
headerAndRows: HeaderAndRows;
|
headerAndRows: HeaderAndRows;
|
||||||
dimensionsSpec: DimensionsSpec;
|
dimensionsSpec: DimensionsSpec;
|
||||||
|
@ -103,7 +97,7 @@ export class SchemaTable extends React.PureComponent<SchemaTableProps> {
|
||||||
headerClassName: columnClassName,
|
headerClassName: columnClassName,
|
||||||
className: columnClassName,
|
className: columnClassName,
|
||||||
id: String(i),
|
id: String(i),
|
||||||
accessor: row => (row.parsed ? row.parsed[columnName] : null),
|
accessor: (row: SampleEntry) => (row.parsed ? row.parsed[columnName] : null),
|
||||||
Cell: row => <TableCell value={row.value} />,
|
Cell: row => <TableCell value={row.value} />,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -24,11 +24,11 @@ import { TableCell } from '../../../components';
|
||||||
import { caseInsensitiveContains, filterMap } from '../../../utils';
|
import { caseInsensitiveContains, filterMap } from '../../../utils';
|
||||||
import { escapeColumnName } from '../../../utils/druid-expression';
|
import { escapeColumnName } from '../../../utils/druid-expression';
|
||||||
import { Transform } from '../../../utils/ingestion-spec';
|
import { Transform } from '../../../utils/ingestion-spec';
|
||||||
import { HeaderAndRows } from '../../../utils/sampler';
|
import { HeaderAndRows, SampleEntry } from '../../../utils/sampler';
|
||||||
|
|
||||||
import './transform-table.scss';
|
import './transform-table.scss';
|
||||||
|
|
||||||
export interface TransformTableProps extends React.Props<any> {
|
export interface TransformTableProps {
|
||||||
sampleData: HeaderAndRows;
|
sampleData: HeaderAndRows;
|
||||||
columnFilter: string;
|
columnFilter: string;
|
||||||
transformedColumnsOnly: boolean;
|
transformedColumnsOnly: boolean;
|
||||||
|
@ -91,7 +91,7 @@ export class TransformTable extends React.PureComponent<TransformTableProps> {
|
||||||
headerClassName: columnClassName,
|
headerClassName: columnClassName,
|
||||||
className: columnClassName,
|
className: columnClassName,
|
||||||
id: String(i),
|
id: String(i),
|
||||||
accessor: row => (row.parsed ? row.parsed[columnName] : null),
|
accessor: (row: SampleEntry) => (row.parsed ? row.parsed[columnName] : null),
|
||||||
Cell: row => <TableCell value={row.value} timestamp={timestamp} />,
|
Cell: row => <TableCell value={row.value} timestamp={timestamp} />,
|
||||||
};
|
};
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -7,10 +7,9 @@ exports[`lookups view matches snapshot 1`] = `
|
||||||
<ViewControlBar
|
<ViewControlBar
|
||||||
label="Lookups"
|
label="Lookups"
|
||||||
>
|
>
|
||||||
<Blueprint3.Button
|
<RefreshButton
|
||||||
icon="refresh"
|
localStorageKey="lookups-refresh-rate"
|
||||||
onClick={[Function]}
|
onRefresh={[Function]}
|
||||||
text="Refresh"
|
|
||||||
/>
|
/>
|
||||||
<Blueprint3.Button
|
<Blueprint3.Button
|
||||||
icon="plus"
|
icon="plus"
|
||||||
|
@ -90,28 +89,28 @@ exports[`lookups view matches snapshot 1`] = `
|
||||||
Array [
|
Array [
|
||||||
Object {
|
Object {
|
||||||
"Header": "Lookup name",
|
"Header": "Lookup name",
|
||||||
"accessor": [Function],
|
"accessor": "id",
|
||||||
"filterable": true,
|
"filterable": true,
|
||||||
"id": "lookup_name",
|
"id": "lookup_name",
|
||||||
"show": true,
|
"show": true,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"Header": "Tier",
|
"Header": "Tier",
|
||||||
"accessor": [Function],
|
"accessor": "tier",
|
||||||
"filterable": true,
|
"filterable": true,
|
||||||
"id": "tier",
|
"id": "tier",
|
||||||
"show": true,
|
"show": true,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"Header": "Type",
|
"Header": "Type",
|
||||||
"accessor": [Function],
|
"accessor": "spec.type",
|
||||||
"filterable": true,
|
"filterable": true,
|
||||||
"id": "type",
|
"id": "type",
|
||||||
"show": true,
|
"show": true,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"Header": "Version",
|
"Header": "Version",
|
||||||
"accessor": [Function],
|
"accessor": "version",
|
||||||
"filterable": true,
|
"filterable": true,
|
||||||
"id": "version",
|
"id": "version",
|
||||||
"show": true,
|
"show": true,
|
||||||
|
|
|
@ -16,18 +16,17 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Button, Icon, Intent, Popover, Position } from '@blueprintjs/core';
|
import { Button, Intent } from '@blueprintjs/core';
|
||||||
import { IconNames } from '@blueprintjs/icons';
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import classNames from 'classnames';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactTable from 'react-table';
|
import ReactTable from 'react-table';
|
||||||
|
|
||||||
import { ActionCell, TableColumnSelector, ViewControlBar } from '../../components';
|
import { ActionCell, RefreshButton, TableColumnSelector, ViewControlBar } from '../../components';
|
||||||
import { AsyncActionDialog, LookupEditDialog } from '../../dialogs/';
|
import { AsyncActionDialog, LookupEditDialog } from '../../dialogs/';
|
||||||
import { AppToaster } from '../../singletons/toaster';
|
import { AppToaster } from '../../singletons/toaster';
|
||||||
import { getDruidErrorMessage, LocalStorageKeys, QueryManager } from '../../utils';
|
import { getDruidErrorMessage, LocalStorageKeys, QueryManager } from '../../utils';
|
||||||
import { BasicAction, basicActionsToMenu } from '../../utils/basic-action';
|
import { BasicAction } from '../../utils/basic-action';
|
||||||
import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array';
|
import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array';
|
||||||
|
|
||||||
import './lookups-view.scss';
|
import './lookups-view.scss';
|
||||||
|
@ -36,7 +35,7 @@ const tableColumns: string[] = ['Lookup name', 'Tier', 'Type', 'Version', Action
|
||||||
|
|
||||||
const DEFAULT_LOOKUP_TIER: string = '__default';
|
const DEFAULT_LOOKUP_TIER: string = '__default';
|
||||||
|
|
||||||
export interface LookupsViewProps extends React.Props<any> {}
|
export interface LookupsViewProps {}
|
||||||
|
|
||||||
export interface LookupsViewState {
|
export interface LookupsViewState {
|
||||||
lookups: {}[] | null;
|
lookups: {}[] | null;
|
||||||
|
@ -58,7 +57,7 @@ export interface LookupsViewState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsViewState> {
|
export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsViewState> {
|
||||||
private lookupsGetQueryManager: QueryManager<string, { lookupEntries: any[]; tiers: string[] }>;
|
private lookupsQueryManager: QueryManager<null, { lookupEntries: any[]; tiers: string[] }>;
|
||||||
|
|
||||||
constructor(props: LookupsViewProps, context: any) {
|
constructor(props: LookupsViewProps, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
@ -82,11 +81,9 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
||||||
LocalStorageKeys.LOOKUP_TABLE_COLUMN_SELECTION,
|
LocalStorageKeys.LOOKUP_TABLE_COLUMN_SELECTION,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount(): void {
|
this.lookupsQueryManager = new QueryManager({
|
||||||
this.lookupsGetQueryManager = new QueryManager({
|
processQuery: async () => {
|
||||||
processQuery: async (query: string) => {
|
|
||||||
const tiersResp = await axios.get('/druid/coordinator/v1/lookups/config?discover=true');
|
const tiersResp = await axios.get('/druid/coordinator/v1/lookups/config?discover=true');
|
||||||
const tiers =
|
const tiers =
|
||||||
tiersResp.data && tiersResp.data.length > 0 ? tiersResp.data : [DEFAULT_LOOKUP_TIER];
|
tiersResp.data && tiersResp.data.length > 0 ? tiersResp.data : [DEFAULT_LOOKUP_TIER];
|
||||||
|
@ -121,18 +118,20 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.lookupsGetQueryManager.runQuery('dummy');
|
componentDidMount(): void {
|
||||||
|
this.lookupsQueryManager.runQuery(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
componentWillUnmount(): void {
|
||||||
this.lookupsGetQueryManager.terminate();
|
this.lookupsQueryManager.terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initializeLookup() {
|
private async initializeLookup() {
|
||||||
try {
|
try {
|
||||||
await axios.post(`/druid/coordinator/v1/lookups/config`, {});
|
await axios.post(`/druid/coordinator/v1/lookups/config`, {});
|
||||||
this.lookupsGetQueryManager.rerunLastQuery();
|
this.lookupsQueryManager.rerunLastQuery();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
icon: IconNames.ERROR,
|
icon: IconNames.ERROR,
|
||||||
|
@ -208,7 +207,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
||||||
this.setState({
|
this.setState({
|
||||||
lookupEditDialogOpen: false,
|
lookupEditDialogOpen: false,
|
||||||
});
|
});
|
||||||
this.lookupsGetQueryManager.rerunLastQuery();
|
this.lookupsQueryManager.rerunLastQuery();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
icon: IconNames.ERROR,
|
icon: IconNames.ERROR,
|
||||||
|
@ -254,7 +253,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
||||||
intent={Intent.DANGER}
|
intent={Intent.DANGER}
|
||||||
onClose={success => {
|
onClose={success => {
|
||||||
this.setState({ deleteLookupTier: null, deleteLookupName: null });
|
this.setState({ deleteLookupTier: null, deleteLookupName: null });
|
||||||
if (success) this.lookupsGetQueryManager.rerunLastQuery();
|
if (success) this.lookupsQueryManager.rerunLastQuery();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<p>{`Are you sure you want to delete the lookup '${deleteLookupName}'?`}</p>
|
<p>{`Are you sure you want to delete the lookup '${deleteLookupName}'?`}</p>
|
||||||
|
@ -296,28 +295,28 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
||||||
{
|
{
|
||||||
Header: 'Lookup name',
|
Header: 'Lookup name',
|
||||||
id: 'lookup_name',
|
id: 'lookup_name',
|
||||||
accessor: (row: any) => row.id,
|
accessor: 'id',
|
||||||
filterable: true,
|
filterable: true,
|
||||||
show: hiddenColumns.exists('Lookup name'),
|
show: hiddenColumns.exists('Lookup name'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Tier',
|
Header: 'Tier',
|
||||||
id: 'tier',
|
id: 'tier',
|
||||||
accessor: (row: any) => row.tier,
|
accessor: 'tier',
|
||||||
filterable: true,
|
filterable: true,
|
||||||
show: hiddenColumns.exists('Tier'),
|
show: hiddenColumns.exists('Tier'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Type',
|
Header: 'Type',
|
||||||
id: 'type',
|
id: 'type',
|
||||||
accessor: (row: any) => row.spec.type,
|
accessor: 'spec.type',
|
||||||
filterable: true,
|
filterable: true,
|
||||||
show: hiddenColumns.exists('Type'),
|
show: hiddenColumns.exists('Type'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Version',
|
Header: 'Version',
|
||||||
id: 'version',
|
id: 'version',
|
||||||
accessor: (row: any) => row.version,
|
accessor: 'version',
|
||||||
filterable: true,
|
filterable: true,
|
||||||
show: hiddenColumns.exists('Version'),
|
show: hiddenColumns.exists('Version'),
|
||||||
},
|
},
|
||||||
|
@ -325,7 +324,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
||||||
Header: ActionCell.COLUMN_LABEL,
|
Header: ActionCell.COLUMN_LABEL,
|
||||||
id: ActionCell.COLUMN_ID,
|
id: ActionCell.COLUMN_ID,
|
||||||
width: ActionCell.COLUMN_WIDTH,
|
width: ActionCell.COLUMN_WIDTH,
|
||||||
accessor: row => ({ id: row.id, tier: row.tier }),
|
accessor: (row: any) => ({ id: row.id, tier: row.tier }),
|
||||||
filterable: false,
|
filterable: false,
|
||||||
Cell: (row: any) => {
|
Cell: (row: any) => {
|
||||||
const lookupId = row.value.id;
|
const lookupId = row.value.id;
|
||||||
|
@ -375,10 +374,9 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
||||||
return (
|
return (
|
||||||
<div className="lookups-view app-view">
|
<div className="lookups-view app-view">
|
||||||
<ViewControlBar label="Lookups">
|
<ViewControlBar label="Lookups">
|
||||||
<Button
|
<RefreshButton
|
||||||
icon={IconNames.REFRESH}
|
onRefresh={() => this.lookupsQueryManager.rerunLastQuery()}
|
||||||
text="Refresh"
|
localStorageKey={LocalStorageKeys.LOOKUPS_REFRESH_RATE}
|
||||||
onClick={() => this.lookupsGetQueryManager.rerunLastQuery()}
|
|
||||||
/>
|
/>
|
||||||
{!lookupsError && (
|
{!lookupsError && (
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -26,7 +26,7 @@ import { ColumnMetadata } from '../../../utils/column-metadata';
|
||||||
|
|
||||||
import './column-tree.scss';
|
import './column-tree.scss';
|
||||||
|
|
||||||
export interface ColumnTreeProps extends React.Props<any> {
|
export interface ColumnTreeProps {
|
||||||
columnMetadataLoading: boolean;
|
columnMetadataLoading: boolean;
|
||||||
columnMetadata: ColumnMetadata[] | null;
|
columnMetadata: ColumnMetadata[] | null;
|
||||||
onQueryStringChange: (queryString: string) => void;
|
onQueryStringChange: (queryString: string) => void;
|
||||||
|
@ -150,11 +150,7 @@ export class ColumnTree extends React.PureComponent<ColumnTreeProps, ColumnTreeS
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleNodeClick = (
|
private handleNodeClick = (nodeData: ITreeNode, nodePath: number[]) => {
|
||||||
nodeData: ITreeNode,
|
|
||||||
nodePath: number[],
|
|
||||||
e: React.MouseEvent<HTMLElement>,
|
|
||||||
) => {
|
|
||||||
const { onQueryStringChange } = this.props;
|
const { onQueryStringChange } = this.props;
|
||||||
const { columnTree, selectedTreeIndex } = this.state;
|
const { columnTree, selectedTreeIndex } = this.state;
|
||||||
if (!columnTree) return;
|
if (!columnTree) return;
|
||||||
|
|
|
@ -44,7 +44,7 @@ export interface QueryExtraInfoData {
|
||||||
wrappedLimit?: number;
|
wrappedLimit?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryExtraInfoProps extends React.Props<any> {
|
export interface QueryExtraInfoProps {
|
||||||
queryExtraInfo: QueryExtraInfoData;
|
queryExtraInfo: QueryExtraInfoData;
|
||||||
onDownload: (filename: string, format: string) => void;
|
onDownload: (filename: string, format: string) => void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ import './query-input.scss';
|
||||||
|
|
||||||
const langTools = ace.acequire('ace/ext/language_tools');
|
const langTools = ace.acequire('ace/ext/language_tools');
|
||||||
|
|
||||||
export interface QueryInputProps extends React.Props<any> {
|
export interface QueryInputProps {
|
||||||
queryString: string;
|
queryString: string;
|
||||||
onQueryStringChange: (newQueryString: string) => void;
|
onQueryStringChange: (newQueryString: string) => void;
|
||||||
runeMode: boolean;
|
runeMode: boolean;
|
||||||
|
@ -71,7 +71,7 @@ export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputS
|
||||||
);
|
);
|
||||||
|
|
||||||
langTools.addCompleter({
|
langTools.addCompleter({
|
||||||
getCompletions: (editor: any, session: any, pos: any, prefix: any, callback: any) => {
|
getCompletions: (_editor: any, _session: any, _pos: any, _prefix: any, callback: any) => {
|
||||||
callback(null, completions);
|
callback(null, completions);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -103,7 +103,7 @@ export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputS
|
||||||
);
|
);
|
||||||
|
|
||||||
const keywordCompleter = {
|
const keywordCompleter = {
|
||||||
getCompletions: (editor: any, session: any, pos: any, prefix: any, callback: any) => {
|
getCompletions: (_editor: any, _session: any, _pos: any, _prefix: any, callback: any) => {
|
||||||
return callback(null, keywordList);
|
return callback(null, keywordList);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -137,7 +137,7 @@ export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputS
|
||||||
});
|
});
|
||||||
|
|
||||||
langTools.addCompleter({
|
langTools.addCompleter({
|
||||||
getCompletions: (editor: any, session: any, pos: any, prefix: any, callback: any) => {
|
getCompletions: (_editor: any, _session: any, _pos: any, _prefix: any, callback: any) => {
|
||||||
callback(null, functionList);
|
callback(null, functionList);
|
||||||
},
|
},
|
||||||
getDocTooltip: (item: any) => {
|
getDocTooltip: (item: any) => {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue