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(
|
||||
"/",
|
||||
"/coordinator-console/*",
|
||||
"/assets/*",
|
||||
"/public/*",
|
||||
"/old-console/*",
|
||||
"/pages/*",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "web-console",
|
||||
"version": "0.15.0",
|
||||
"version": "0.16.0",
|
||||
"description": "A web console for Apache Druid",
|
||||
"author": "Imply Data Inc.",
|
||||
"license": "Apache-2.0",
|
||||
|
@ -37,7 +37,7 @@
|
|||
"compile": "./script/build",
|
||||
"pretest": "./script/build",
|
||||
"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",
|
||||
"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)'",
|
||||
|
|
|
@ -85,7 +85,7 @@ import React from 'react';
|
|||
|
||||
import './${name}.scss';
|
||||
|
||||
export interface ${camelName}Props extends React.Props<any> {
|
||||
export interface ${camelName}Props {
|
||||
}
|
||||
|
||||
export interface ${camelName}State {
|
||||
|
@ -100,7 +100,7 @@ export class ${camelName} extends React.PureComponent<${camelName}Props, ${camel
|
|||
|
||||
render() {
|
||||
return <div className="${name}">
|
||||
|
||||
Stuff...
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import React from 'react';
|
|||
|
||||
import './react-table-custom-pagination.scss';
|
||||
|
||||
interface ReactTableCustomPaginationProps extends React.Props<any> {
|
||||
interface ReactTableCustomPaginationProps {
|
||||
pages: number;
|
||||
page: number;
|
||||
showPageSizeOptions: boolean;
|
||||
|
|
|
@ -38,7 +38,7 @@ class NoData extends React.PureComponent {
|
|||
|
||||
Object.assign(ReactTableDefaults, {
|
||||
className: '-striped -highlight',
|
||||
defaultFilterMethod: (filter: Filter, row: any, column: any) => {
|
||||
defaultFilterMethod: (filter: Filter, row: any) => {
|
||||
const id = filter.pivotId || filter.id;
|
||||
return booleanCustomTableFilter(filter, row[id]);
|
||||
},
|
||||
|
|
|
@ -25,7 +25,7 @@ import { ActionIcon } from '../action-icon/action-icon';
|
|||
|
||||
import './action-cell.scss';
|
||||
|
||||
export interface ActionCellProps extends React.Props<any> {
|
||||
export interface ActionCellProps {
|
||||
onDetail?: () => void;
|
||||
actions?: BasicAction[];
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import React from 'react';
|
|||
|
||||
import './action-icon.scss';
|
||||
|
||||
export interface ActionIconProps extends React.Props<any> {
|
||||
export interface ActionIconProps {
|
||||
className?: string;
|
||||
icon: IconName;
|
||||
onClick?: () => void;
|
||||
|
|
|
@ -190,7 +190,7 @@ exports[`auto-form snapshot matches snapshot 1`] = `
|
|||
class="bp3-form-content"
|
||||
>
|
||||
<div
|
||||
class="bp3-input-group"
|
||||
class="bp3-input-group suggestible-input"
|
||||
>
|
||||
<input
|
||||
class="bp3-input"
|
||||
|
|
|
@ -35,7 +35,7 @@ describe('auto-form snapshot', () => {
|
|||
{ name: 'testSeven', type: 'json' },
|
||||
]}
|
||||
model={String}
|
||||
onChange={(newModel: Record<string, any>) => {}}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
);
|
||||
const { container } = render(autoForm);
|
||||
|
|
|
@ -16,32 +16,17 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Button,
|
||||
FormGroup,
|
||||
HTMLSelect,
|
||||
Icon,
|
||||
InputGroup,
|
||||
Menu,
|
||||
MenuItem,
|
||||
NumericInput,
|
||||
Popover,
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
import { FormGroup, HTMLSelect, Icon, NumericInput, Popover } from '@blueprintjs/core';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import React from 'react';
|
||||
|
||||
import { deepDelete, deepGet, deepSet } from '../../utils/object-change';
|
||||
import { ArrayInput } from '../array-input/array-input';
|
||||
import { JSONInput } from '../json-input/json-input';
|
||||
import { SuggestibleInput, SuggestionGroup } from '../suggestible-input/suggestible-input';
|
||||
|
||||
import './auto-form.scss';
|
||||
|
||||
export interface SuggestionGroup {
|
||||
group: string;
|
||||
suggestions: string[];
|
||||
}
|
||||
|
||||
export interface Field<T> {
|
||||
name: string;
|
||||
label?: string;
|
||||
|
@ -55,7 +40,7 @@ export interface Field<T> {
|
|||
min?: number;
|
||||
}
|
||||
|
||||
export interface AutoFormProps<T> extends React.Props<any> {
|
||||
export interface AutoFormProps<T> {
|
||||
fields: Field<T>[];
|
||||
model: T | null;
|
||||
onChange: (newModel: T) => void;
|
||||
|
@ -64,13 +49,13 @@ export interface AutoFormProps<T> extends React.Props<any> {
|
|||
large?: boolean;
|
||||
}
|
||||
|
||||
export interface AutoFormState<T> {
|
||||
export interface AutoFormState {
|
||||
jsonInputsValidity: any;
|
||||
}
|
||||
|
||||
export class AutoForm<T extends Record<string, any>> extends React.PureComponent<
|
||||
AutoFormProps<T>,
|
||||
AutoFormState<T>
|
||||
AutoFormState
|
||||
> {
|
||||
static makeLabelName(label: string): string {
|
||||
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 {
|
||||
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);
|
||||
return (
|
||||
<InputGroup
|
||||
<SuggestibleInput
|
||||
value={modalValue != null ? modalValue : field.defaultValue || ''}
|
||||
onChange={(e: any) => {
|
||||
let v = e.target.value;
|
||||
onValueChange={v => {
|
||||
if (sanitize) v = sanitize(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);
|
||||
}}
|
||||
placeholder={field.placeholder}
|
||||
rightElement={
|
||||
suggestionsMenu && (
|
||||
<Popover content={suggestionsMenu} position={Position.BOTTOM_RIGHT} autoFocus={false}>
|
||||
<Button icon={IconNames.CARET_DOWN} minimal />
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
suggestions={field.suggestions}
|
||||
large={large}
|
||||
disabled={field.disabled}
|
||||
/>
|
||||
|
|
|
@ -20,7 +20,7 @@ import React from 'react';
|
|||
|
||||
import './center-message.scss';
|
||||
|
||||
export interface CenterMessageProps extends React.Props<any> {}
|
||||
export interface CenterMessageProps {}
|
||||
|
||||
export class CenterMessage extends React.PureComponent<CenterMessageProps> {
|
||||
render() {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`decribe clearable-input matches snapshot 1`] = `
|
||||
exports[`clearable-input matches snapshot 1`] = `
|
||||
<div
|
||||
class="bp3-input-group clearable-input testClassName"
|
||||
>
|
||||
|
|
|
@ -21,16 +21,16 @@ import { render } from 'react-testing-library';
|
|||
|
||||
import { ClearableInput } from './clearable-input';
|
||||
|
||||
describe('decribe clearable-input', () => {
|
||||
describe('clearable-input', () => {
|
||||
it('matches snapshot', () => {
|
||||
const centerMessage = (
|
||||
<ClearableInput
|
||||
className={'testClassName'}
|
||||
value={'testValue'}
|
||||
placeholder={'testPlaceholder'}
|
||||
onChange={(value: string) => null}
|
||||
onChange={() => null}
|
||||
>
|
||||
;<div>Hello World</div>
|
||||
<div>Hello World</div>
|
||||
</ClearableInput>
|
||||
);
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ import { IconNames } from '@blueprintjs/icons';
|
|||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
|
||||
export interface ClearableInputProps extends React.Props<any> {
|
||||
export interface ClearableInputProps {
|
||||
className?: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
export interface ExternalLinkProps extends React.Props<any> {
|
||||
export interface ExternalLinkProps {
|
||||
href: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ import {
|
|||
Button,
|
||||
Intent,
|
||||
Menu,
|
||||
MenuDivider,
|
||||
MenuItem,
|
||||
Navbar,
|
||||
NavbarDivider,
|
||||
|
@ -34,8 +33,8 @@ import { IconNames } from '@blueprintjs/icons';
|
|||
import React from 'react';
|
||||
|
||||
import { AboutDialog } from '../../dialogs/about-dialog/about-dialog';
|
||||
import { CoordinatorDynamicConfigDialog } from '../../dialogs/coordinator-dynamic-config/coordinator-dynamic-config';
|
||||
import { OverlordDynamicConfigDialog } from '../../dialogs/overlord-dynamic-config/overlord-dynamic-config';
|
||||
import { CoordinatorDynamicConfigDialog } from '../../dialogs/coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog';
|
||||
import { OverlordDynamicConfigDialog } from '../../dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog';
|
||||
import {
|
||||
DRUID_DOCS,
|
||||
DRUID_GITHUB,
|
||||
|
@ -56,7 +55,7 @@ export type HeaderActiveTab =
|
|||
| 'servers'
|
||||
| 'lookups';
|
||||
|
||||
export interface HeaderBarProps extends React.Props<any> {
|
||||
export interface HeaderBarProps {
|
||||
active: HeaderActiveTab;
|
||||
hideLegacy: boolean;
|
||||
}
|
||||
|
|
|
@ -17,10 +17,9 @@
|
|||
*/
|
||||
|
||||
import { Button, Collapse, TextArea } from '@blueprintjs/core';
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
|
||||
interface JSONCollapseProps extends React.Props<any> {
|
||||
interface JSONCollapseProps {
|
||||
stringValue: string;
|
||||
buttonText: string;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import { JSONInput } from './json-input';
|
|||
|
||||
describe('json input', () => {
|
||||
it('matches snapshot', () => {
|
||||
const jsonCollapse = <JSONInput onChange={(newJSONValue: any) => {}} value={'test'} />;
|
||||
const jsonCollapse = <JSONInput onChange={() => {}} value={'test'} />;
|
||||
const { container } = render(jsonCollapse);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
|
|
@ -21,7 +21,7 @@ import AceEditor from 'react-ace';
|
|||
|
||||
import { parseStringToJSON, stringifyJSON, validJson } from '../../utils';
|
||||
|
||||
interface JSONInputProps extends React.Props<any> {
|
||||
interface JSONInputProps {
|
||||
onChange: (newJSONValue: any) => void;
|
||||
value: any;
|
||||
updateInputValidity?: (valueValid: boolean) => void;
|
||||
|
|
|
@ -20,7 +20,7 @@ import React from 'react';
|
|||
|
||||
import './loader.scss';
|
||||
|
||||
export interface LoaderProps extends React.Props<any> {
|
||||
export interface LoaderProps {
|
||||
loadingText?: string;
|
||||
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 { TimedButton } from '../timed-button/timed-button';
|
||||
|
||||
export interface RefreshButtonProps extends React.Props<any> {
|
||||
export interface RefreshButtonProps {
|
||||
onRefresh: (auto: boolean) => void;
|
||||
localStorageKey?: LocalStorageKeys;
|
||||
}
|
||||
|
@ -45,10 +45,10 @@ export class RefreshButton extends React.PureComponent<RefreshButtonProps> {
|
|||
return (
|
||||
<TimedButton
|
||||
defaultValue={30000}
|
||||
label={'Auto refresh every:'}
|
||||
label="Auto refresh every:"
|
||||
intervals={intervals}
|
||||
icon={IconNames.REFRESH}
|
||||
text={'Refresh'}
|
||||
text="Refresh"
|
||||
onRefresh={onRefresh}
|
||||
localStorageKey={localStorageKey}
|
||||
/>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
import React from 'react';
|
||||
import { render } from 'react-testing-library';
|
||||
|
||||
import { Rule, RuleEditor } from './rule-editor';
|
||||
import { RuleEditor } from './rule-editor';
|
||||
|
||||
describe('rule editor', () => {
|
||||
it('matches snapshot', () => {
|
||||
|
@ -27,7 +27,7 @@ describe('rule editor', () => {
|
|||
<RuleEditor
|
||||
rule={{ type: 'loadForever' }}
|
||||
tiers={['test', 'test', 'test']}
|
||||
onChange={(newRule: Rule) => null}
|
||||
onChange={() => null}
|
||||
onDelete={() => null}
|
||||
moveUp={null}
|
||||
moveDown={null}
|
||||
|
|
|
@ -28,7 +28,6 @@ import {
|
|||
TagInput,
|
||||
} from '@blueprintjs/core';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import axios from 'axios';
|
||||
import React from 'react';
|
||||
|
||||
import './rule-editor.scss';
|
||||
|
@ -53,7 +52,7 @@ export interface Rule {
|
|||
export type LoadType = 'load' | 'drop' | 'broadcast';
|
||||
export type TimeType = 'Forever' | 'ByInterval' | 'ByPeriod';
|
||||
|
||||
export interface RuleEditorProps extends React.Props<any> {
|
||||
export interface RuleEditorProps {
|
||||
rule: Rule;
|
||||
tiers: any[];
|
||||
onChange: (newRule: Rule) => void;
|
||||
|
@ -241,7 +240,7 @@ export class RuleEditor extends React.PureComponent<RuleEditorProps, RuleEditorS
|
|||
}
|
||||
|
||||
render() {
|
||||
const { tiers, onChange, rule, onDelete, moveUp, moveDown } = this.props;
|
||||
const { onChange, rule, onDelete, moveUp, moveDown } = this.props;
|
||||
const { isOpen } = this.state;
|
||||
|
||||
if (!rule) return null;
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* 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 copy from 'copy-to-clipboard';
|
||||
import React from 'react';
|
||||
|
@ -27,7 +27,7 @@ import { downloadFile } from '../../utils';
|
|||
|
||||
import './show-json.scss';
|
||||
|
||||
export interface ShowJsonProps extends React.Props<any> {
|
||||
export interface ShowJsonProps {
|
||||
endpoint: string;
|
||||
transform?: (x: any) => any;
|
||||
downloadFilename?: string;
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* 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 copy from 'copy-to-clipboard';
|
||||
import React from 'react';
|
||||
|
@ -35,7 +35,7 @@ function removeFirstPartialLine(log: string): string {
|
|||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export interface ShowLogProps extends React.Props<any> {
|
||||
export interface ShowLogProps {
|
||||
endpoint: string;
|
||||
downloadFilename?: string;
|
||||
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 { 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', () => {
|
||||
const lookupEditDialog = (
|
||||
<LookupEditDialog
|
||||
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 suggestibleInput = (
|
||||
<SuggestibleInput onValueChange={() => {}} suggestions={['a', 'b', 'c']} />
|
||||
);
|
||||
const { container } = render(lookupEditDialog, { container: document.body });
|
||||
|
||||
const { container } = render(suggestibleInput);
|
||||
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.
|
||||
*/
|
||||
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import React from 'react';
|
||||
|
||||
import { AppToaster } from '../../singletons/toaster';
|
||||
import { ActionIcon } from '../action-icon/action-icon';
|
||||
|
||||
import './table-cell.scss';
|
||||
|
||||
export interface NullTableCellProps extends React.Props<any> {
|
||||
export interface NullTableCellProps {
|
||||
value?: any;
|
||||
timestamp?: boolean;
|
||||
unparseable?: boolean;
|
||||
|
|
|
@ -26,7 +26,7 @@ describe('table column', () => {
|
|||
const tableColumn = (
|
||||
<TableColumnSelector
|
||||
columns={['a', 'b', 'c']}
|
||||
onChange={(column: string) => {}}
|
||||
onChange={() => {}}
|
||||
tableColumnsHidden={['a', 'b', 'c']}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* 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 React from 'react';
|
||||
|
||||
|
@ -24,7 +24,7 @@ import { MenuCheckbox } from '../menu-checkbox/menu-checkbox';
|
|||
|
||||
import './table-column-selector.scss';
|
||||
|
||||
interface TableColumnSelectorProps extends React.Props<any> {
|
||||
interface TableColumnSelectorProps {
|
||||
columns: string[];
|
||||
onChange: (column: string) => void;
|
||||
tableColumnsHidden: string[];
|
||||
|
|
|
@ -18,5 +18,4 @@
|
|||
|
||||
.timed-button {
|
||||
padding: 10px 10px 5px 10px;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -41,7 +41,9 @@ import {
|
|||
|
||||
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;
|
||||
baseURL?: string;
|
||||
customHeaderName?: string;
|
||||
|
@ -60,11 +62,9 @@ export class ConsoleApplication extends React.PureComponent<
|
|||
> {
|
||||
static MESSAGE_KEY = 'druid-console-message';
|
||||
static MESSAGE_DISMISSED = 'dismissed';
|
||||
private capabilitiesQueryManager: QueryManager<string, string>;
|
||||
private capabilitiesQueryManager: QueryManager<string, Capabilities>;
|
||||
|
||||
static async discoverCapabilities(): Promise<
|
||||
'working-with-sql' | 'working-without-sql' | 'broken'
|
||||
> {
|
||||
static async discoverCapabilities(): Promise<Capabilities> {
|
||||
try {
|
||||
await axios.post('/druid/v2/sql', { query: 'SELECT 1337' });
|
||||
} catch (e) {
|
||||
|
@ -109,13 +109,13 @@ export class ConsoleApplication extends React.PureComponent<
|
|||
});
|
||||
}
|
||||
|
||||
private supervisorId: string | null;
|
||||
private taskId: string | null;
|
||||
private openDialog: string | null;
|
||||
private datasource: string | null;
|
||||
private onlyUnavailable: boolean | null;
|
||||
private initQuery: string | null;
|
||||
private middleManager: string | null;
|
||||
private supervisorId: string | undefined;
|
||||
private taskId: string | undefined;
|
||||
private openDialog: string | undefined;
|
||||
private datasource: string | undefined;
|
||||
private onlyUnavailable: boolean | undefined;
|
||||
private initQuery: string | undefined;
|
||||
private middleManager: string | undefined;
|
||||
|
||||
constructor(props: ConsoleApplicationProps, context: any) {
|
||||
super(props, context);
|
||||
|
@ -134,16 +134,16 @@ export class ConsoleApplication extends React.PureComponent<
|
|||
}
|
||||
|
||||
this.capabilitiesQueryManager = new QueryManager({
|
||||
processQuery: async (query: string) => {
|
||||
processQuery: async () => {
|
||||
const capabilities = await ConsoleApplication.discoverCapabilities();
|
||||
if (capabilities !== 'working-with-sql') {
|
||||
ConsoleApplication.shownNotifications(capabilities);
|
||||
}
|
||||
return capabilities;
|
||||
},
|
||||
onStateChange: ({ result, loading, error }) => {
|
||||
onStateChange: ({ result, loading }) => {
|
||||
this.setState({
|
||||
noSqlMode: result === 'working-with-sql' ? false : true,
|
||||
noSqlMode: result !== 'working-with-sql',
|
||||
capabilitiesLoading: loading,
|
||||
});
|
||||
},
|
||||
|
@ -160,13 +160,13 @@ export class ConsoleApplication extends React.PureComponent<
|
|||
|
||||
private resetInitialsWithDelay() {
|
||||
setTimeout(() => {
|
||||
this.taskId = null;
|
||||
this.supervisorId = null;
|
||||
this.openDialog = null;
|
||||
this.datasource = null;
|
||||
this.onlyUnavailable = null;
|
||||
this.initQuery = null;
|
||||
this.middleManager = null;
|
||||
this.taskId = undefined;
|
||||
this.supervisorId = undefined;
|
||||
this.openDialog = undefined;
|
||||
this.datasource = undefined;
|
||||
this.onlyUnavailable = undefined;
|
||||
this.initQuery = undefined;
|
||||
this.middleManager = undefined;
|
||||
}, 50);
|
||||
}
|
||||
|
||||
|
@ -177,7 +177,7 @@ export class ConsoleApplication extends React.PureComponent<
|
|||
this.resetInitialsWithDelay();
|
||||
};
|
||||
|
||||
private goToTask = (taskId: string | null, openDialog?: string) => {
|
||||
private goToTask = (taskId: string | undefined, openDialog?: string) => {
|
||||
this.taskId = taskId;
|
||||
if (openDialog) this.openDialog = openDialog;
|
||||
window.location.hash = 'tasks';
|
||||
|
|
|
@ -24,7 +24,7 @@ import { AboutDialog } from './about-dialog';
|
|||
describe('about dialog', () => {
|
||||
it('matches snapshot', () => {
|
||||
const aboutDialog = <AboutDialog onClose={() => null} />;
|
||||
const { container } = render(aboutDialog, { container: document.body });
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
render(aboutDialog);
|
||||
expect(document.body.lastChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -28,7 +28,7 @@ import {
|
|||
DRUID_WEBSITE,
|
||||
} from '../../variables';
|
||||
|
||||
export interface AboutDialogProps extends React.Props<any> {
|
||||
export interface AboutDialogProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,13 +28,13 @@ describe('async action dialog', () => {
|
|||
action={() => {
|
||||
return Promise.resolve();
|
||||
}}
|
||||
onClose={(success: boolean) => null}
|
||||
onClose={() => null}
|
||||
confirmButtonText={'test'}
|
||||
successText={'test'}
|
||||
failText={'test'}
|
||||
/>
|
||||
);
|
||||
const { container } = render(asyncActionDialog, { container: document.body });
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
render(asyncActionDialog);
|
||||
expect(document.body.lastChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,25 +16,14 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Classes,
|
||||
Dialog,
|
||||
FormGroup,
|
||||
Icon,
|
||||
Intent,
|
||||
NumericInput,
|
||||
ProgressBar,
|
||||
TagInput,
|
||||
} from '@blueprintjs/core';
|
||||
import { Button, Classes, Dialog, Icon, Intent, ProgressBar } from '@blueprintjs/core';
|
||||
import { IconName } from '@blueprintjs/icons';
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
|
||||
import { AppToaster } from '../../singletons/toaster';
|
||||
|
||||
export interface AsyncAlertDialogProps extends React.Props<any> {
|
||||
export interface AsyncAlertDialogProps {
|
||||
action: null | (() => Promise<void>);
|
||||
onClose: (success: boolean) => void;
|
||||
confirmButtonText: string;
|
||||
|
|
|
@ -240,7 +240,7 @@ exports[`compaction dialog matches snapshot 1`] = `
|
|||
class="bp3-form-content"
|
||||
>
|
||||
<div
|
||||
class="bp3-input-group"
|
||||
class="bp3-input-group suggestible-input"
|
||||
>
|
||||
<input
|
||||
class="bp3-input"
|
||||
|
|
|
@ -26,13 +26,13 @@ describe('compaction dialog', () => {
|
|||
const compactionDialog = (
|
||||
<CompactionDialog
|
||||
onClose={() => null}
|
||||
onSave={(config: any) => null}
|
||||
onSave={() => null}
|
||||
onDelete={() => null}
|
||||
datasource={'test'}
|
||||
configData={'test'}
|
||||
/>
|
||||
);
|
||||
const { container } = render(compactionDialog, { container: document.body });
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
render(compactionDialog);
|
||||
expect(document.body.lastChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,7 +23,7 @@ import { AutoForm } from '../../components';
|
|||
|
||||
import './compaction-dialog.scss';
|
||||
|
||||
export interface CompactionDialogProps extends React.Props<any> {
|
||||
export interface CompactionDialogProps {
|
||||
onClose: () => void;
|
||||
onSave: (config: any) => void;
|
||||
onDelete: () => void;
|
||||
|
|
|
@ -16,7 +16,7 @@ exports[`coordinator dynamic config matches snapshot 1`] = `
|
|||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="bp3-dialog snitch-dialog coordinator-dynamic-config"
|
||||
class="bp3-dialog snitch-dialog coordinator-dynamic-config-dialog"
|
||||
>
|
||||
<div
|
||||
class="bp3-dialog-header"
|
||||
|
@ -76,16 +76,6 @@ exports[`coordinator dynamic config matches snapshot 1`] = `
|
|||
<div
|
||||
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
|
||||
class="bp3-button bp3-intent-primary"
|
||||
type="button"
|
|
@ -16,7 +16,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.coordinator-dynamic-config {
|
||||
.coordinator-dynamic-config-dialog {
|
||||
&.bp3-dialog {
|
||||
margin-top: 5vh;
|
||||
top: 5%;
|
|
@ -19,12 +19,12 @@
|
|||
import React from 'react';
|
||||
import { render } from 'react-testing-library';
|
||||
|
||||
import { CoordinatorDynamicConfigDialog } from './coordinator-dynamic-config';
|
||||
import { CoordinatorDynamicConfigDialog } from './coordinator-dynamic-config-dialog';
|
||||
|
||||
describe('coordinator dynamic config', () => {
|
||||
it('matches snapshot', () => {
|
||||
const coordinatorDynamicConfig = <CoordinatorDynamicConfigDialog onClose={() => null} />;
|
||||
const { container } = render(coordinatorDynamicConfig, { container: document.body });
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
render(coordinatorDynamicConfig);
|
||||
expect(document.body.lastChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -26,9 +26,9 @@ import { AppToaster } from '../../singletons/toaster';
|
|||
import { getDruidErrorMessage, QueryManager } from '../../utils';
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ export class CoordinatorDynamicConfigDialog extends React.PureComponent<
|
|||
CoordinatorDynamicConfigDialogProps,
|
||||
CoordinatorDynamicConfigDialogState
|
||||
> {
|
||||
private historyQueryManager: QueryManager<string, any>;
|
||||
private historyQueryManager: QueryManager<null, any>;
|
||||
|
||||
constructor(props: CoordinatorDynamicConfigDialogProps) {
|
||||
super(props);
|
||||
|
@ -49,24 +49,24 @@ export class CoordinatorDynamicConfigDialog extends React.PureComponent<
|
|||
dynamicConfig: null,
|
||||
historyRecords: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getClusterConfig();
|
||||
|
||||
this.historyQueryManager = new QueryManager({
|
||||
processQuery: async query => {
|
||||
processQuery: async () => {
|
||||
const historyResp = await axios(`/druid/coordinator/v1/config/history?count=100`);
|
||||
return historyResp.data;
|
||||
},
|
||||
onStateChange: ({ result, loading, error }) => {
|
||||
onStateChange: ({ result }) => {
|
||||
this.setState({
|
||||
historyRecords: result,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this.historyQueryManager.runQuery(`dummy`);
|
||||
componentDidMount() {
|
||||
this.getClusterConfig();
|
||||
|
||||
this.historyQueryManager.runQuery(null);
|
||||
}
|
||||
|
||||
async getClusterConfig() {
|
||||
|
@ -118,7 +118,7 @@ export class CoordinatorDynamicConfigDialog extends React.PureComponent<
|
|||
|
||||
return (
|
||||
<SnitchDialog
|
||||
className="coordinator-dynamic-config"
|
||||
className="coordinator-dynamic-config-dialog"
|
||||
isOpen
|
||||
onSave={this.saveClusterConfig}
|
||||
onClose={onClose}
|
|
@ -32,7 +32,7 @@ describe('history dialog', () => {
|
|||
isOpen
|
||||
/>
|
||||
);
|
||||
const { container } = render(historyDialog, { container: document.body });
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
render(historyDialog);
|
||||
expect(document.body.lastChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -24,7 +24,7 @@ import { JSONCollapse } from '../../components';
|
|||
import './history-dialog.scss';
|
||||
|
||||
interface HistoryDialogProps extends IDialogProps {
|
||||
historyRecords: any;
|
||||
historyRecords: any[];
|
||||
}
|
||||
|
||||
interface HistoryDialogState {}
|
||||
|
@ -45,13 +45,13 @@ export class HistoryDialog extends React.PureComponent<HistoryDialogProps, Histo
|
|||
<>
|
||||
<span className="history-dialog-title">History</span>
|
||||
<div className="history-record-entries">
|
||||
{historyRecords.map((record: any) => {
|
||||
{historyRecords.map((record, i) => {
|
||||
const auditInfo = record.auditInfo;
|
||||
const auditTime = record.auditTime;
|
||||
const formattedTime = auditTime.replace('T', ' ').substring(0, auditTime.length - 5);
|
||||
|
||||
return (
|
||||
<div key={record.auditTime} className="history-record-entry">
|
||||
<div key={i} className="history-record-entry">
|
||||
<Card>
|
||||
<div className="history-record-title">
|
||||
<span className="history-record-title-change">Change</span>
|
||||
|
|
|
@ -18,10 +18,10 @@
|
|||
export * from './about-dialog/about-dialog';
|
||||
export * from './async-action-dialog/async-action-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 './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 './retention-dialog/retention-dialog';
|
||||
export * from './snitch-dialog/snitch-dialog';
|
||||
|
|
|
@ -38,7 +38,7 @@ describe('lookup edit dialog', () => {
|
|||
/>
|
||||
);
|
||||
|
||||
const { container } = render(lookupEditDialog, { container: document.body });
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
render(lookupEditDialog);
|
||||
expect(document.body.lastChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -32,7 +32,7 @@ import { validJson } from '../../utils';
|
|||
|
||||
import './lookup-edit-dialog.scss';
|
||||
|
||||
export interface LookupEditDialogProps extends React.Props<any> {
|
||||
export interface LookupEditDialogProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit: () => void;
|
||||
|
@ -105,7 +105,6 @@ export class LookupEditDialog extends React.PureComponent<
|
|||
lookupVersion,
|
||||
onChange,
|
||||
isEdit,
|
||||
allLookupTiers,
|
||||
} = this.props;
|
||||
|
||||
const disableSubmit =
|
||||
|
@ -155,8 +154,6 @@ export class LookupEditDialog extends React.PureComponent<
|
|||
value={lookupSpec}
|
||||
editorProps={{ $blockScrolling: Infinity }}
|
||||
setOptions={{
|
||||
enableBasicAutocompletion: false,
|
||||
enableLiveAutocompletion: false,
|
||||
tabSize: 2,
|
||||
}}
|
||||
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.
|
||||
*/
|
||||
|
||||
.overlord-dynamic-config {
|
||||
.overlord-dynamic-config-dialog {
|
||||
&.bp3-dialog {
|
||||
margin-top: 5vh;
|
||||
top: 5%;
|
|
@ -26,9 +26,9 @@ import { AppToaster } from '../../singletons/toaster';
|
|||
import { getDruidErrorMessage, QueryManager } from '../../utils';
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -51,22 +51,22 @@ export class OverlordDynamicConfigDialog extends React.PureComponent<
|
|||
allJSONValid: true,
|
||||
historyRecords: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getConfig();
|
||||
|
||||
this.historyQueryManager = new QueryManager({
|
||||
processQuery: async query => {
|
||||
processQuery: async () => {
|
||||
const historyResp = await axios(`/druid/indexer/v1/worker/history?count=100`);
|
||||
return historyResp.data;
|
||||
},
|
||||
onStateChange: ({ result, loading, error }) => {
|
||||
onStateChange: ({ result }) => {
|
||||
this.setState({
|
||||
historyRecords: result,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getConfig();
|
||||
|
||||
this.historyQueryManager.runQuery(`dummy`);
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ export class OverlordDynamicConfigDialog extends React.PureComponent<
|
|||
|
||||
return (
|
||||
<SnitchDialog
|
||||
className="overlord-dynamic-config"
|
||||
className="overlord-dynamic-config-dialog"
|
||||
isOpen
|
||||
onSave={this.saveConfig}
|
||||
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}
|
||||
/>
|
||||
);
|
||||
const { container } = render(queryPlanDialog, { container: document.body });
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
render(queryPlanDialog);
|
||||
expect(document.body.lastChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,7 +23,7 @@ import { BasicQueryExplanation, SemiJoinQueryExplanation } from '../../utils';
|
|||
|
||||
import './query-plan-dialog.scss';
|
||||
|
||||
export interface QueryPlanDialogProps extends React.Props<any> {
|
||||
export interface QueryPlanDialogProps {
|
||||
explainResult: BasicQueryExplanation | SemiJoinQueryExplanation | string | null;
|
||||
explainError: Error | null;
|
||||
onClose: () => void;
|
||||
|
|
|
@ -116,16 +116,6 @@ exports[`retention dialog matches snapshot 1`] = `
|
|||
<div
|
||||
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
|
||||
class="bp3-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 { render } from 'react-testing-library';
|
||||
|
||||
import { RetentionDialog } from './retention-dialog';
|
||||
import { reorderArray, RetentionDialog } from './retention-dialog';
|
||||
|
||||
describe('retention dialog', () => {
|
||||
it('matches snapshot', () => {
|
||||
|
@ -33,7 +33,42 @@ describe('retention dialog', () => {
|
|||
onSave={() => null}
|
||||
/>
|
||||
);
|
||||
const { container } = render(retentionDialog, { container: document.body });
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
render(retentionDialog);
|
||||
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 React from 'react';
|
||||
|
||||
import { Rule, RuleEditor } from '../../components';
|
||||
import { RuleEditor } from '../../components';
|
||||
import { QueryManager } from '../../utils';
|
||||
import { SnitchDialog } from '../snitch-dialog/snitch-dialog';
|
||||
|
||||
|
@ -37,7 +37,7 @@ export function reorderArray<T>(items: T[], oldIndex: number, newIndex: number):
|
|||
return newItems;
|
||||
}
|
||||
|
||||
export interface RetentionDialogProps extends React.Props<any> {
|
||||
export interface RetentionDialogProps {
|
||||
datasource: string;
|
||||
rules: any[];
|
||||
tiers: string[];
|
||||
|
@ -64,23 +64,23 @@ export class RetentionDialog extends React.PureComponent<
|
|||
currentRules: props.rules,
|
||||
historyRecords: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { datasource } = this.props;
|
||||
this.historyQueryManager = new QueryManager({
|
||||
processQuery: async query => {
|
||||
processQuery: async datasource => {
|
||||
const historyResp = await axios(`/druid/coordinator/v1/rules/${datasource}/history`);
|
||||
return historyResp.data;
|
||||
},
|
||||
onStateChange: ({ result, loading, error }) => {
|
||||
onStateChange: ({ result }) => {
|
||||
this.setState({
|
||||
historyRecords: result,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this.historyQueryManager.runQuery(`dummy`);
|
||||
componentDidMount() {
|
||||
const { datasource } = this.props;
|
||||
this.historyQueryManager.runQuery(datasource);
|
||||
}
|
||||
|
||||
private save = (comment: string) => {
|
||||
|
@ -106,7 +106,7 @@ export class RetentionDialog extends React.PureComponent<
|
|||
onDeleteRule = (index: number) => {
|
||||
const { currentRules } = this.state;
|
||||
|
||||
const newRules = (currentRules || []).filter((r, i) => i !== index);
|
||||
const newRules = (currentRules || []).filter((_r, i) => i !== index);
|
||||
|
||||
this.setState({
|
||||
currentRules: newRules,
|
||||
|
|
|
@ -148,7 +148,18 @@ exports[`task table action dialog matches snapshot 1`] = `
|
|||
>
|
||||
<div
|
||||
class="footer-actions-left"
|
||||
/>
|
||||
>
|
||||
<button
|
||||
class="bp3-button"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="bp3-button-text"
|
||||
>
|
||||
test
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="bp3-dialog-footer-actions"
|
||||
>
|
||||
|
|
|
@ -21,19 +21,18 @@ import { render } from 'react-testing-library';
|
|||
|
||||
import { SegmentTableActionDialog } from './segment-table-action-dialog';
|
||||
|
||||
const basicAction = { title: 'test', onAction: () => null };
|
||||
describe('task table action dialog', () => {
|
||||
it('matches snapshot', () => {
|
||||
const taskTableActionDialog = (
|
||||
<SegmentTableActionDialog
|
||||
dataSourceId="test"
|
||||
segmentId="test"
|
||||
actions={[]}
|
||||
actions={[{ title: 'test', onAction: () => null }]}
|
||||
onClose={() => null}
|
||||
isOpen
|
||||
/>
|
||||
);
|
||||
const { container } = render(taskTableActionDialog, { container: document.body });
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
render(taskTableActionDialog);
|
||||
expect(document.body.lastChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { IDialogProps, TextArea } from '@blueprintjs/core';
|
||||
import { IDialogProps } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
|
||||
import { ShowJson } from '../../components';
|
||||
|
|
|
@ -24,7 +24,7 @@ exports[`clipboard dialog matches snapshot 1`] = `
|
|||
<h4
|
||||
class="bp3-heading"
|
||||
>
|
||||
Show value
|
||||
Full value
|
||||
</h4>
|
||||
<button
|
||||
aria-label="Close"
|
||||
|
|
|
@ -16,18 +16,17 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.show-value-dialog{
|
||||
&.bp3-dialog{
|
||||
.show-value-dialog {
|
||||
&.bp3-dialog {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.bp3-input{
|
||||
.bp3-input {
|
||||
margin: 10px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.bp3-dialog-footer-actions{
|
||||
.bp3-dialog-footer-actions {
|
||||
padding-right: 10px;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ describe('clipboard dialog', () => {
|
|||
}
|
||||
/>
|
||||
);
|
||||
const { container } = render(compactionDialog, { container: document.body });
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
render(compactionDialog);
|
||||
expect(document.body.lastChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,16 +15,16 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* 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 copy = require('copy-to-clipboard');
|
||||
import copy from 'copy-to-clipboard';
|
||||
import React from 'react';
|
||||
|
||||
import { AppToaster } from '../../singletons/toaster';
|
||||
|
||||
import './show-value-dialog.scss';
|
||||
|
||||
export interface ShowValueDialogProps extends React.Props<any> {
|
||||
export interface ShowValueDialogProps {
|
||||
onClose: () => void;
|
||||
str: string;
|
||||
}
|
||||
|
@ -39,23 +39,22 @@ export class ShowValueDialog extends React.PureComponent<ShowValueDialogProps> {
|
|||
const { onClose, str } = this.props;
|
||||
|
||||
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} />
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
<Button
|
||||
icon={IconNames.DUPLICATE}
|
||||
text={'Copy'}
|
||||
onClick={() => {
|
||||
<Button icon={IconNames.DUPLICATE} text={'Copy'} onClick={this.handleCopy} />
|
||||
<Button text={'Close'} intent={Intent.PRIMARY} onClick={onClose} />
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
private handleCopy = () => {
|
||||
const { str } = this.props;
|
||||
copy(str, { format: 'text/plain' });
|
||||
AppToaster.show({
|
||||
message: 'Value copied to clipboard',
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Button text={'Close'} intent={'primary'} onClick={onClose} />
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import { SnitchDialog } from './snitch-dialog';
|
|||
describe('snitch dialog', () => {
|
||||
it('matches snapshot', () => {
|
||||
const snitchDialog = <SnitchDialog onSave={() => null} isOpen />;
|
||||
const { container } = render(snitchDialog, { container: document.body });
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
render(snitchDialog);
|
||||
expect(document.body.lastChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -44,7 +44,6 @@ export interface SnitchDialogState {
|
|||
comment: string;
|
||||
|
||||
showFinalStep?: boolean;
|
||||
saveDisabled?: boolean;
|
||||
|
||||
showHistory?: boolean;
|
||||
}
|
||||
|
@ -55,7 +54,6 @@ export class SnitchDialog extends React.PureComponent<SnitchDialogProps, SnitchD
|
|||
|
||||
this.state = {
|
||||
comment: '',
|
||||
saveDisabled: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -68,11 +66,8 @@ export class SnitchDialog extends React.PureComponent<SnitchDialogProps, SnitchD
|
|||
};
|
||||
|
||||
changeComment(newComment: string) {
|
||||
const { comment } = this.state;
|
||||
|
||||
this.setState({
|
||||
comment: newComment,
|
||||
saveDisabled: !newComment,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -95,15 +90,14 @@ export class SnitchDialog extends React.PureComponent<SnitchDialogProps, SnitchD
|
|||
});
|
||||
};
|
||||
|
||||
goToHistory = () => {
|
||||
handleGoToHistory = () => {
|
||||
this.setState({
|
||||
showHistory: true,
|
||||
});
|
||||
};
|
||||
|
||||
renderFinalStep() {
|
||||
const { onClose, children } = this.props;
|
||||
const { saveDisabled, comment } = this.state;
|
||||
const { comment } = this.state;
|
||||
|
||||
return (
|
||||
<Dialog {...this.props}>
|
||||
|
@ -117,14 +111,14 @@ export class SnitchDialog extends React.PureComponent<SnitchDialogProps, SnitchD
|
|||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
|
||||
<div className={Classes.DIALOG_FOOTER}>{this.renderActions(saveDisabled)}</div>
|
||||
<div className={Classes.DIALOG_FOOTER}>{this.renderActions(!comment)}</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
renderHistoryDialog() {
|
||||
const { historyRecords } = this.props;
|
||||
if (!historyRecords) return;
|
||||
return (
|
||||
<HistoryDialog {...this.props} className="history-dialog" historyRecords={historyRecords}>
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
|
@ -142,8 +136,13 @@ export class SnitchDialog extends React.PureComponent<SnitchDialogProps, SnitchD
|
|||
|
||||
return (
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
{showFinalStep || historyRecords === undefined ? null : (
|
||||
<Button className="left-align-button" minimal text="History" onClick={this.goToHistory} />
|
||||
{!showFinalStep && historyRecords && (
|
||||
<Button
|
||||
className="left-align-button"
|
||||
minimal
|
||||
text="History"
|
||||
onClick={this.handleGoToHistory}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showFinalStep ? (
|
||||
|
@ -178,7 +177,7 @@ export class SnitchDialog extends React.PureComponent<SnitchDialogProps, SnitchD
|
|||
}
|
||||
|
||||
render() {
|
||||
const { onClose, className, children, saveDisabled } = this.props;
|
||||
const { children, saveDisabled } = this.props;
|
||||
const { showFinalStep, showHistory } = this.state;
|
||||
|
||||
if (showFinalStep) return this.renderFinalStep();
|
||||
|
@ -189,7 +188,6 @@ export class SnitchDialog extends React.PureComponent<SnitchDialogProps, SnitchD
|
|||
return (
|
||||
<Dialog isOpen {...propsClone}>
|
||||
<div className={Classes.DIALOG_BODY}>{children}</div>
|
||||
|
||||
<div className={Classes.DIALOG_FOOTER}>{this.renderActions(saveDisabled)}</div>
|
||||
</Dialog>
|
||||
);
|
||||
|
|
|
@ -23,10 +23,8 @@ import { SpecDialog } from './spec-dialog';
|
|||
|
||||
describe('spec dialog', () => {
|
||||
it('matches snapshot', () => {
|
||||
const specDialog = (
|
||||
<SpecDialog onSubmit={(spec: JSON) => null} onClose={() => null} title={'test'} />
|
||||
);
|
||||
const { container } = render(specDialog, { container: document.body });
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
const specDialog = <SpecDialog onSubmit={() => null} onClose={() => null} title={'test'} />;
|
||||
render(specDialog);
|
||||
expect(document.body.lastChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,7 +22,7 @@ import AceEditor from 'react-ace';
|
|||
|
||||
import './spec-dialog.scss';
|
||||
|
||||
export interface SpecDialogProps extends React.Props<any> {
|
||||
export interface SpecDialogProps {
|
||||
onSubmit: (spec: JSON) => void;
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
|
@ -84,10 +84,7 @@ export class SpecDialog extends React.PureComponent<SpecDialogProps, SpecDialogS
|
|||
value={spec}
|
||||
width="100%"
|
||||
setOptions={{
|
||||
enableBasicAutocompletion: true,
|
||||
enableLiveAutocompletion: true,
|
||||
showLineNumbers: true,
|
||||
enableSnippets: true,
|
||||
tabSize: 2,
|
||||
}}
|
||||
style={{}}
|
||||
|
|
|
@ -32,7 +32,7 @@ describe('supervisor table action dialog', () => {
|
|||
isOpen
|
||||
/>
|
||||
);
|
||||
const { container } = render(supervisorTableActionDialog, { container: document.body });
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
render(supervisorTableActionDialog);
|
||||
expect(document.body.lastChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -75,7 +75,7 @@ $side-bar-width: 120px;
|
|||
left: $side-bar-width;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
padding: 10px 20px;
|
||||
padding: 10px 20px 15px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ describe('table action dialog', () => {
|
|||
isOpen
|
||||
/>
|
||||
);
|
||||
const { container } = render(tableActionDialog, { container: document.body });
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
render(tableActionDialog);
|
||||
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}>
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
<div className="side-bar">
|
||||
{sideButtonMetadata.map((d: SideButtonMetaData) => (
|
||||
{sideButtonMetadata.map((d, i) => (
|
||||
<Button
|
||||
className="tab-button"
|
||||
icon={<Icon icon={d.icon} iconSize={20} />}
|
||||
key={d.text}
|
||||
key={i}
|
||||
text={d.text}
|
||||
intent={d.active ? Intent.PRIMARY : Intent.NONE}
|
||||
minimal={!d.active}
|
||||
|
|
|
@ -33,7 +33,7 @@ describe('task table action dialog', () => {
|
|||
isOpen
|
||||
/>
|
||||
);
|
||||
const { container } = render(taskTableActionDialog, { container: document.body });
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
render(taskTableActionDialog);
|
||||
expect(document.body.lastChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* 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 FileSaver from 'file-saver';
|
||||
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}`;
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -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[] {
|
||||
return (xs.map(f) as any).filter(Boolean);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
*/
|
||||
|
||||
import { Code } from '@blueprintjs/core';
|
||||
import { number } from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import { Field } from '../components/auto-form/auto-form';
|
||||
|
@ -184,11 +183,12 @@ export interface ParseSpec {
|
|||
|
||||
export function hasParallelAbility(spec: IngestionSpec): boolean {
|
||||
const specType = getSpecType(spec);
|
||||
return spec.type === 'index' || spec.type === 'index_parallel';
|
||||
return specType === 'index' || specType === 'index_parallel';
|
||||
}
|
||||
|
||||
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';
|
||||
|
@ -1482,6 +1482,45 @@ const TUNING_CONFIG_FORM_FIELDS: Field<TuningConfig>[] = [
|
|||
placeholder: 'Default: 1/6 of max JVM memory',
|
||||
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',
|
||||
type: 'duration',
|
||||
|
@ -1513,10 +1552,6 @@ const TUNING_CONFIG_FORM_FIELDS: Field<TuningConfig>[] = [
|
|||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'forceExtendableShardSpecs',
|
||||
type: 'boolean',
|
||||
},
|
||||
{
|
||||
name: 'pushTimeout',
|
||||
type: 'number',
|
||||
|
|
|
@ -25,24 +25,21 @@ export class LocalStorageBackedArray<T> {
|
|||
constructor(key: LocalStorageKeys, array?: T[]) {
|
||||
this.key = key;
|
||||
if (!Array.isArray(array)) {
|
||||
this.getDataFromStorage();
|
||||
this.storedArray = this.getDataFromStorage();
|
||||
} else {
|
||||
this.storedArray = array;
|
||||
this.setDataInStorage();
|
||||
}
|
||||
}
|
||||
|
||||
private getDataFromStorage(): void {
|
||||
let possibleArray: any;
|
||||
private getDataFromStorage(): T[] {
|
||||
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 {
|
||||
// show all columns by default
|
||||
possibleArray = [];
|
||||
return [];
|
||||
}
|
||||
if (!Array.isArray(possibleArray)) possibleArray = [];
|
||||
|
||||
this.storedArray = possibleArray;
|
||||
}
|
||||
|
||||
private setDataInStorage(): void {
|
||||
|
|
|
@ -32,6 +32,7 @@ export const LocalStorageKeys = {
|
|||
SEGMENTS_REFRESH_RATE: 'segments-refresh-rate' as 'segments-refresh-rate',
|
||||
SERVERS_REFRESH_RATE: 'servers-refresh-rate' as 'servers-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];
|
||||
|
||||
|
|
|
@ -36,8 +36,8 @@ export class QueryManager<Q, R> {
|
|||
private onStateChange?: (queryResolve: QueryStateInt<R>) => void;
|
||||
|
||||
private terminated = false;
|
||||
private nextQuery: Q;
|
||||
private lastQuery: Q;
|
||||
private nextQuery: Q | undefined;
|
||||
private lastQuery: Q | undefined;
|
||||
private actuallyLoading = false;
|
||||
private state: QueryStateInt<R> = {
|
||||
result: null,
|
||||
|
@ -73,6 +73,7 @@ export class QueryManager<Q, R> {
|
|||
|
||||
private run() {
|
||||
this.lastQuery = this.nextQuery;
|
||||
if (typeof this.lastQuery === 'undefined') return;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ exports[`data source view matches snapshot 1`] = `
|
|||
Array [
|
||||
"Datasource",
|
||||
"Availability",
|
||||
"Segment load/drop",
|
||||
"Retention",
|
||||
"Compaction",
|
||||
"Size",
|
||||
|
@ -110,6 +111,14 @@ exports[`data source view matches snapshot 1`] = `
|
|||
"show": true,
|
||||
"sortMethod": [Function],
|
||||
},
|
||||
Object {
|
||||
"Cell": [Function],
|
||||
"Header": "Segment load/drop",
|
||||
"accessor": "num_segments_to_load",
|
||||
"filterable": false,
|
||||
"id": "load-drop",
|
||||
"show": true,
|
||||
},
|
||||
Object {
|
||||
"Cell": [Function],
|
||||
"Header": "Retention",
|
||||
|
|
|
@ -24,11 +24,7 @@ import { DatasourcesView } from './datasource-view';
|
|||
describe('data source view', () => {
|
||||
it('matches snapshot', () => {
|
||||
const dataSourceView = shallow(
|
||||
<DatasourcesView
|
||||
goToQuery={(initSql: string) => {}}
|
||||
goToSegments={(datasource: string, onlyUnavailable?: boolean) => {}}
|
||||
noSqlMode={false}
|
||||
/>,
|
||||
<DatasourcesView goToQuery={() => {}} goToSegments={() => {}} noSqlMode={false} />,
|
||||
);
|
||||
expect(dataSourceView).toMatchSnapshot();
|
||||
});
|
||||
|
|
|
@ -16,16 +16,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Button,
|
||||
FormGroup,
|
||||
Icon,
|
||||
InputGroup,
|
||||
Intent,
|
||||
Popover,
|
||||
Position,
|
||||
Switch,
|
||||
} from '@blueprintjs/core';
|
||||
import { Button, FormGroup, InputGroup, Intent, Switch } from '@blueprintjs/core';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import axios from 'axios';
|
||||
import React from 'react';
|
||||
|
@ -55,12 +46,14 @@ import {
|
|||
} from '../../utils';
|
||||
import { BasicAction } from '../../utils/basic-action';
|
||||
import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array';
|
||||
import { deepGet } from '../../utils/object-change';
|
||||
|
||||
import './datasource-view.scss';
|
||||
|
||||
const tableColumns: string[] = [
|
||||
'Datasource',
|
||||
'Availability',
|
||||
'Segment load/drop',
|
||||
'Retention',
|
||||
'Compaction',
|
||||
'Size',
|
||||
|
@ -70,13 +63,25 @@ const tableColumns: string[] = [
|
|||
const tableColumnsNoSql: string[] = [
|
||||
'Datasource',
|
||||
'Availability',
|
||||
'Segment load/drop',
|
||||
'Retention',
|
||||
'Compaction',
|
||||
'Size',
|
||||
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;
|
||||
goToSegments: (datasource: string, onlyUnavailable?: boolean) => void;
|
||||
noSqlMode: boolean;
|
||||
|
@ -90,10 +95,12 @@ interface Datasource {
|
|||
|
||||
interface DatasourceQueryResultRow {
|
||||
datasource: string;
|
||||
num_available_segments: number;
|
||||
num_rows: number;
|
||||
num_segments: number;
|
||||
num_available_segments: number;
|
||||
num_segments_to_load: number;
|
||||
num_segments_to_drop: number;
|
||||
size: number;
|
||||
num_rows: number;
|
||||
}
|
||||
|
||||
export interface DatasourcesViewState {
|
||||
|
@ -124,6 +131,17 @@ export class DatasourcesView extends React.PureComponent<
|
|||
static FULLY_AVAILABLE_COLOR = '#57d500';
|
||||
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 {
|
||||
if (rules.length === 0) {
|
||||
return 'No rules';
|
||||
|
@ -135,7 +153,7 @@ export class DatasourcesView extends React.PureComponent<
|
|||
}
|
||||
|
||||
private datasourceQueryManager: QueryManager<
|
||||
string,
|
||||
boolean,
|
||||
{ tiers: string[]; defaultRules: any[]; datasources: Datasource[] }
|
||||
>;
|
||||
|
||||
|
@ -162,30 +180,31 @@ export class DatasourcesView extends React.PureComponent<
|
|||
LocalStorageKeys.DATASOURCE_TABLE_COLUMN_SELECTION,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
const { noSqlMode } = this.props;
|
||||
const { hiddenColumns } = this.state;
|
||||
|
||||
this.datasourceQueryManager = new QueryManager({
|
||||
processQuery: async (query: string) => {
|
||||
processQuery: async noSqlMode => {
|
||||
let datasources: DatasourceQueryResultRow[];
|
||||
if (!noSqlMode) {
|
||||
datasources = await queryDruidSql({ query });
|
||||
datasources = await queryDruidSql({ query: DatasourcesView.DATASOURCE_SQL });
|
||||
} else {
|
||||
const datasourcesResp = await axios.get('/druid/coordinator/v1/datasources?simple');
|
||||
const loadstatusResp = await axios.get('/druid/coordinator/v1/loadstatus?simple');
|
||||
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 {
|
||||
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,
|
||||
num_segments: d.properties.segments.count + loadstatus[d.name],
|
||||
num_rows: -1,
|
||||
};
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const seen = countBy(datasources, (x: any) => x.datasource);
|
||||
|
@ -234,15 +253,11 @@ export class DatasourcesView extends React.PureComponent<
|
|||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this.datasourceQueryManager.runQuery(`SELECT
|
||||
datasource,
|
||||
COUNT(*) AS num_segments,
|
||||
SUM(is_available) AS num_available_segments,
|
||||
SUM("size") AS size,
|
||||
SUM("num_rows") AS num_rows
|
||||
FROM sys.segments
|
||||
GROUP BY 1`);
|
||||
componentDidMount(): void {
|
||||
const { noSqlMode } = this.props;
|
||||
this.datasourceQueryManager.runQuery(noSqlMode);
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
|
@ -581,7 +596,7 @@ GROUP BY 1`);
|
|||
}
|
||||
filterable
|
||||
filtered={datasourcesFilter}
|
||||
onFilteredChange={(filtered, column) => {
|
||||
onFilteredChange={filtered => {
|
||||
this.setState({ datasourcesFilter: filtered });
|
||||
}}
|
||||
columns={[
|
||||
|
@ -669,6 +684,17 @@ GROUP BY 1`);
|
|||
},
|
||||
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',
|
||||
id: 'retention',
|
||||
|
@ -789,7 +815,7 @@ GROUP BY 1`);
|
|||
<Button
|
||||
icon={IconNames.APPLICATION}
|
||||
text="Go to SQL"
|
||||
onClick={() => goToQuery(this.datasourceQueryManager.getLastQuery())}
|
||||
onClick={() => goToQuery(DatasourcesView.DATASOURCE_SQL)}
|
||||
/>
|
||||
)}
|
||||
<Switch
|
||||
|
|
|
@ -19,10 +19,12 @@
|
|||
import { Card, H5, Icon } from '@blueprintjs/core';
|
||||
import { IconName, IconNames } from '@blueprintjs/icons';
|
||||
import axios from 'axios';
|
||||
import { sum } from 'd3-array';
|
||||
import React from 'react';
|
||||
|
||||
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';
|
||||
|
||||
|
@ -35,7 +37,7 @@ export interface CardOptions {
|
|||
error?: string | null;
|
||||
}
|
||||
|
||||
export interface HomeViewProps extends React.Props<any> {
|
||||
export interface HomeViewProps {
|
||||
noSqlMode: boolean;
|
||||
}
|
||||
|
||||
|
@ -50,6 +52,7 @@ export interface HomeViewState {
|
|||
|
||||
segmentCountLoading: boolean;
|
||||
segmentCount: number;
|
||||
unavailableSegmentCount: number;
|
||||
segmentCountError: string | null;
|
||||
|
||||
supervisorCountLoading: boolean;
|
||||
|
@ -77,12 +80,12 @@ export interface HomeViewState {
|
|||
}
|
||||
|
||||
export class HomeView extends React.PureComponent<HomeViewProps, HomeViewState> {
|
||||
private statusQueryManager: QueryManager<string, any>;
|
||||
private datasourceQueryManager: QueryManager<string, any>;
|
||||
private segmentQueryManager: QueryManager<string, any>;
|
||||
private supervisorQueryManager: QueryManager<string, any>;
|
||||
private taskQueryManager: QueryManager<string, any>;
|
||||
private serverQueryManager: QueryManager<string, any>;
|
||||
private statusQueryManager: QueryManager<null, any>;
|
||||
private datasourceQueryManager: QueryManager<boolean, any>;
|
||||
private segmentQueryManager: QueryManager<boolean, any>;
|
||||
private supervisorQueryManager: QueryManager<null, any>;
|
||||
private taskQueryManager: QueryManager<boolean, any>;
|
||||
private serverQueryManager: QueryManager<boolean, any>;
|
||||
|
||||
constructor(props: HomeViewProps, context: any) {
|
||||
super(props, context);
|
||||
|
@ -97,6 +100,7 @@ export class HomeView extends React.PureComponent<HomeViewProps, HomeViewState>
|
|||
|
||||
segmentCountLoading: false,
|
||||
segmentCount: 0,
|
||||
unavailableSegmentCount: 0,
|
||||
segmentCountError: null,
|
||||
|
||||
supervisorCountLoading: false,
|
||||
|
@ -122,13 +126,9 @@ export class HomeView extends React.PureComponent<HomeViewProps, HomeViewState>
|
|||
peonCount: 0,
|
||||
serverCountError: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
const { noSqlMode } = this.props;
|
||||
|
||||
this.statusQueryManager = new QueryManager({
|
||||
processQuery: async query => {
|
||||
processQuery: async () => {
|
||||
const statusResp = await axios.get('/status');
|
||||
return statusResp.data;
|
||||
},
|
||||
|
@ -141,15 +141,13 @@ export class HomeView extends React.PureComponent<HomeViewProps, HomeViewState>
|
|||
},
|
||||
});
|
||||
|
||||
this.statusQueryManager.runQuery(`dummy`);
|
||||
|
||||
// -------------------------
|
||||
|
||||
this.datasourceQueryManager = new QueryManager({
|
||||
processQuery: async query => {
|
||||
processQuery: async noSqlMode => {
|
||||
let datasources: string[];
|
||||
if (!noSqlMode) {
|
||||
datasources = await queryDruidSql({ query });
|
||||
datasources = await queryDruidSql({
|
||||
query: `SELECT datasource FROM sys.segments GROUP BY 1`,
|
||||
});
|
||||
} else {
|
||||
const datasourcesResp = await axios.get('/druid/coordinator/v1/datasources');
|
||||
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({
|
||||
processQuery: async query => {
|
||||
processQuery: async noSqlMode => {
|
||||
if (noSqlMode) {
|
||||
const loadstatusResp = await axios.get('/druid/coordinator/v1/loadstatus?simple');
|
||||
const loadstatus = loadstatusResp.data;
|
||||
const unavailableSegmentNum = Object.keys(loadstatus).reduce((sum, key) => {
|
||||
return sum + loadstatus[key];
|
||||
}, 0);
|
||||
const unavailableSegmentNum = sum(Object.keys(loadstatus), key => loadstatus[key]);
|
||||
|
||||
const datasourcesMetaResp = await axios.get('/druid/coordinator/v1/datasources?simple');
|
||||
const datasourcesMeta = datasourcesMetaResp.data;
|
||||
const availableSegmentNum = datasourcesMeta.reduce((sum: number, curr: any) => {
|
||||
return sum + curr.properties.segments.count;
|
||||
}, 0);
|
||||
const availableSegmentNum = sum(datasourcesMeta, (curr: any) =>
|
||||
deepGet(curr, 'properties.segments.count'),
|
||||
);
|
||||
|
||||
return availableSegmentNum + unavailableSegmentNum;
|
||||
return {
|
||||
count: availableSegmentNum + unavailableSegmentNum,
|
||||
unavailable: unavailableSegmentNum,
|
||||
};
|
||||
} else {
|
||||
const segments = await queryDruidSql({ query });
|
||||
return getHeadProp(segments, 'count') || 0;
|
||||
const segments = await queryDruidSql({
|
||||
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 }) => {
|
||||
this.setState({
|
||||
segmentCountLoading: loading,
|
||||
segmentCount: result,
|
||||
segmentCount: result ? result.count : 0,
|
||||
unavailableSegmentCount: result ? result.unavailable : 0,
|
||||
segmentCountError: error,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
this.segmentQueryManager.runQuery(`SELECT COUNT(*) as "count" FROM sys.segments`);
|
||||
|
||||
// -------------------------
|
||||
this.supervisorQueryManager = new QueryManager({
|
||||
processQuery: async (query: string) => {
|
||||
processQuery: async () => {
|
||||
const resp = await axios.get('/druid/indexer/v1/supervisor?full');
|
||||
const data = resp.data;
|
||||
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({
|
||||
processQuery: async query => {
|
||||
processQuery: async noSqlMode => {
|
||||
if (noSqlMode) {
|
||||
const completeTasksResp = await axios.get('/druid/indexer/v1/completeTasks');
|
||||
const runningTasksResp = await axios.get('/druid/indexer/v1/runningTasks');
|
||||
|
@ -243,7 +237,11 @@ export class HomeView extends React.PureComponent<HomeViewProps, HomeViewState>
|
|||
};
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
|
@ -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({
|
||||
processQuery: async query => {
|
||||
processQuery: async noSqlMode => {
|
||||
if (noSqlMode) {
|
||||
const serversResp = await axios.get('/druid/coordinator/v1/servers?simple');
|
||||
const middleManagerResp = await axios.get('/druid/indexer/v1/workers');
|
||||
|
@ -283,7 +273,9 @@ GROUP BY 1`);
|
|||
const serverCountsFromQuery: {
|
||||
server_type: string;
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
@ -301,10 +293,17 @@ GROUP BY 1`);
|
|||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this.serverQueryManager.runQuery(
|
||||
`SELECT server_type, COUNT(*) as "count" FROM sys.servers GROUP BY 1`,
|
||||
);
|
||||
componentDidMount(): void {
|
||||
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 {
|
||||
|
@ -362,7 +361,14 @@ GROUP BY 1`);
|
|||
icon: IconNames.STACKED_CHART,
|
||||
title: 'Segments',
|
||||
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,
|
||||
})}
|
||||
{this.renderCard({
|
||||
|
|
|
@ -153,7 +153,7 @@ exports[`load data view matches snapshot 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="main"
|
||||
className="main bp3-input"
|
||||
/>
|
||||
<div
|
||||
className="control"
|
||||
|
|
|
@ -22,12 +22,12 @@ import ReactTable from 'react-table';
|
|||
|
||||
import { TableCell } from '../../../components';
|
||||
import { caseInsensitiveContains, filterMap } from '../../../utils';
|
||||
import { DruidFilter, Transform } from '../../../utils/ingestion-spec';
|
||||
import { HeaderAndRows } from '../../../utils/sampler';
|
||||
import { DruidFilter } from '../../../utils/ingestion-spec';
|
||||
import { HeaderAndRows, SampleEntry } from '../../../utils/sampler';
|
||||
|
||||
import './filter-table.scss';
|
||||
|
||||
export interface FilterTableProps extends React.Props<any> {
|
||||
export interface FilterTableProps {
|
||||
sampleData: HeaderAndRows;
|
||||
columnFilter: string;
|
||||
dimensionFilters: DruidFilter[];
|
||||
|
@ -82,7 +82,7 @@ export class FilterTable extends React.PureComponent<FilterTableProps> {
|
|||
headerClassName: columnClassName,
|
||||
className: columnClassName,
|
||||
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} />,
|
||||
};
|
||||
})}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
.load-data-view {
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-gap: 10px 5px;
|
||||
grid-gap: 15px 5px;
|
||||
grid-template-columns: 1fr 280px;
|
||||
grid-template-rows: 55px 1fr 28px;
|
||||
grid-template-areas:
|
||||
|
@ -29,7 +29,8 @@
|
|||
|
||||
&.welcome {
|
||||
.main {
|
||||
margin-left: -10px;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
|
||||
.bp3-card {
|
||||
position: relative;
|
||||
|
@ -37,8 +38,8 @@
|
|||
vertical-align: top;
|
||||
width: 250px;
|
||||
height: 140px;
|
||||
margin-left: 15px;
|
||||
margin-bottom: 15px;
|
||||
margin-top: 10px;
|
||||
margin-left: 10px;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ import { LoadDataView } from './load-data-view';
|
|||
|
||||
describe('load data view', () => {
|
||||
it('matches snapshot', () => {
|
||||
const loadDataView = shallow(<LoadDataView goToTask={(taskId: string | null) => {}} />);
|
||||
const loadDataView = shallow(<LoadDataView goToTask={() => {}} />);
|
||||
expect(loadDataView).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -25,8 +25,6 @@ import {
|
|||
Card,
|
||||
Classes,
|
||||
Code,
|
||||
Dialog,
|
||||
Elevation,
|
||||
FormGroup,
|
||||
H5,
|
||||
HTMLSelect,
|
||||
|
@ -221,10 +219,10 @@ const VIEW_TITLE: Record<Step, string> = {
|
|||
loading: 'Loading',
|
||||
};
|
||||
|
||||
export interface LoadDataViewProps extends React.Props<any> {
|
||||
export interface LoadDataViewProps {
|
||||
initSupervisorId?: string | null;
|
||||
initTaskId?: string | null;
|
||||
goToTask: (taskId: string | null, supervisor?: string) => void;
|
||||
goToTask: (taskId: string | undefined, supervisor?: string) => void;
|
||||
}
|
||||
|
||||
export interface LoadDataViewState {
|
||||
|
@ -524,7 +522,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="main">
|
||||
<div className="main bp3-input">
|
||||
{this.renderIngestionCard('kafka')}
|
||||
{this.renderIngestionCard('kinesis')}
|
||||
{this.renderIngestionCard('index:static-s3')}
|
||||
|
@ -679,7 +677,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
|
|||
<Button
|
||||
text="Submit task"
|
||||
rightIcon={IconNames.ARROW_RIGHT}
|
||||
onClick={() => goToTask(null, 'task')}
|
||||
onClick={() => goToTask(undefined, 'task')}
|
||||
intent={Intent.PRIMARY}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
@ -695,7 +693,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
|
|||
<Button
|
||||
text="Submit supervisor"
|
||||
rightIcon={IconNames.ARROW_RIGHT}
|
||||
onClick={() => goToTask(null, 'supervisor')}
|
||||
onClick={() => goToTask(undefined, 'supervisor')}
|
||||
intent={Intent.PRIMARY}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
@ -703,7 +701,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
|
|||
<Button
|
||||
text="Submit task"
|
||||
rightIcon={IconNames.ARROW_RIGHT}
|
||||
onClick={() => goToTask(null, 'task')}
|
||||
onClick={() => goToTask(undefined, 'task')}
|
||||
intent={Intent.PRIMARY}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
@ -1602,6 +1600,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
|
|||
return;
|
||||
}
|
||||
|
||||
if (sampleResponse.data.length) {
|
||||
this.setState({
|
||||
cacheKey: sampleResponse.cacheKey,
|
||||
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 => {
|
||||
|
@ -2684,7 +2711,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
|
|||
});
|
||||
|
||||
setTimeout(() => {
|
||||
goToTask(null);
|
||||
goToTask(undefined); // Can we get the supervisor ID here?
|
||||
}, 1000);
|
||||
}
|
||||
}}
|
||||
|
|
|
@ -27,7 +27,7 @@ import { HeaderAndRows, SampleEntry } from '../../../utils/sampler';
|
|||
|
||||
import './parse-data-table.scss';
|
||||
|
||||
export interface ParseDataTableProps extends React.Props<any> {
|
||||
export interface ParseDataTableProps {
|
||||
sampleData: HeaderAndRows;
|
||||
columnFilter: string;
|
||||
canFlatten: boolean;
|
||||
|
|
|
@ -17,4 +17,5 @@
|
|||
*/
|
||||
|
||||
.parse-time-table {
|
||||
position: relative;
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ import { HeaderAndRows, SampleEntry } from '../../../utils/sampler';
|
|||
|
||||
import './parse-time-table.scss';
|
||||
|
||||
export interface ParseTimeTableProps extends React.Props<any> {
|
||||
export interface ParseTimeTableProps {
|
||||
sampleBundle: {
|
||||
headerAndRows: HeaderAndRows;
|
||||
timestampSpec: TimestampSpec;
|
||||
|
|
|
@ -21,12 +21,7 @@ import React from 'react';
|
|||
import ReactTable from 'react-table';
|
||||
|
||||
import { TableCell } from '../../../components';
|
||||
import {
|
||||
alphanumericCompare,
|
||||
caseInsensitiveContains,
|
||||
filterMap,
|
||||
sortWithPrefixSuffix,
|
||||
} from '../../../utils';
|
||||
import { caseInsensitiveContains, filterMap, sortWithPrefixSuffix } from '../../../utils';
|
||||
import {
|
||||
DimensionSpec,
|
||||
DimensionsSpec,
|
||||
|
@ -35,13 +30,12 @@ import {
|
|||
getMetricSpecName,
|
||||
inflateDimensionSpec,
|
||||
MetricSpec,
|
||||
TimestampSpec,
|
||||
} from '../../../utils/ingestion-spec';
|
||||
import { HeaderAndRows, SampleEntry } from '../../../utils/sampler';
|
||||
|
||||
import './schema-table.scss';
|
||||
|
||||
export interface SchemaTableProps extends React.Props<any> {
|
||||
export interface SchemaTableProps {
|
||||
sampleBundle: {
|
||||
headerAndRows: HeaderAndRows;
|
||||
dimensionsSpec: DimensionsSpec;
|
||||
|
@ -103,7 +97,7 @@ export class SchemaTable extends React.PureComponent<SchemaTableProps> {
|
|||
headerClassName: columnClassName,
|
||||
className: columnClassName,
|
||||
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} />,
|
||||
};
|
||||
} else {
|
||||
|
|
|
@ -24,11 +24,11 @@ import { TableCell } from '../../../components';
|
|||
import { caseInsensitiveContains, filterMap } from '../../../utils';
|
||||
import { escapeColumnName } from '../../../utils/druid-expression';
|
||||
import { Transform } from '../../../utils/ingestion-spec';
|
||||
import { HeaderAndRows } from '../../../utils/sampler';
|
||||
import { HeaderAndRows, SampleEntry } from '../../../utils/sampler';
|
||||
|
||||
import './transform-table.scss';
|
||||
|
||||
export interface TransformTableProps extends React.Props<any> {
|
||||
export interface TransformTableProps {
|
||||
sampleData: HeaderAndRows;
|
||||
columnFilter: string;
|
||||
transformedColumnsOnly: boolean;
|
||||
|
@ -91,7 +91,7 @@ export class TransformTable extends React.PureComponent<TransformTableProps> {
|
|||
headerClassName: columnClassName,
|
||||
className: columnClassName,
|
||||
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} />,
|
||||
};
|
||||
})}
|
||||
|
|
|
@ -7,10 +7,9 @@ exports[`lookups view matches snapshot 1`] = `
|
|||
<ViewControlBar
|
||||
label="Lookups"
|
||||
>
|
||||
<Blueprint3.Button
|
||||
icon="refresh"
|
||||
onClick={[Function]}
|
||||
text="Refresh"
|
||||
<RefreshButton
|
||||
localStorageKey="lookups-refresh-rate"
|
||||
onRefresh={[Function]}
|
||||
/>
|
||||
<Blueprint3.Button
|
||||
icon="plus"
|
||||
|
@ -90,28 +89,28 @@ exports[`lookups view matches snapshot 1`] = `
|
|||
Array [
|
||||
Object {
|
||||
"Header": "Lookup name",
|
||||
"accessor": [Function],
|
||||
"accessor": "id",
|
||||
"filterable": true,
|
||||
"id": "lookup_name",
|
||||
"show": true,
|
||||
},
|
||||
Object {
|
||||
"Header": "Tier",
|
||||
"accessor": [Function],
|
||||
"accessor": "tier",
|
||||
"filterable": true,
|
||||
"id": "tier",
|
||||
"show": true,
|
||||
},
|
||||
Object {
|
||||
"Header": "Type",
|
||||
"accessor": [Function],
|
||||
"accessor": "spec.type",
|
||||
"filterable": true,
|
||||
"id": "type",
|
||||
"show": true,
|
||||
},
|
||||
Object {
|
||||
"Header": "Version",
|
||||
"accessor": [Function],
|
||||
"accessor": "version",
|
||||
"filterable": true,
|
||||
"id": "version",
|
||||
"show": true,
|
||||
|
|
|
@ -16,18 +16,17 @@
|
|||
* 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 axios from 'axios';
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
|
||||
import { ActionCell, TableColumnSelector, ViewControlBar } from '../../components';
|
||||
import { ActionCell, RefreshButton, TableColumnSelector, ViewControlBar } from '../../components';
|
||||
import { AsyncActionDialog, LookupEditDialog } from '../../dialogs/';
|
||||
import { AppToaster } from '../../singletons/toaster';
|
||||
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 './lookups-view.scss';
|
||||
|
@ -36,7 +35,7 @@ const tableColumns: string[] = ['Lookup name', 'Tier', 'Type', 'Version', Action
|
|||
|
||||
const DEFAULT_LOOKUP_TIER: string = '__default';
|
||||
|
||||
export interface LookupsViewProps extends React.Props<any> {}
|
||||
export interface LookupsViewProps {}
|
||||
|
||||
export interface LookupsViewState {
|
||||
lookups: {}[] | null;
|
||||
|
@ -58,7 +57,7 @@ export interface 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) {
|
||||
super(props, context);
|
||||
|
@ -82,11 +81,9 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
|||
LocalStorageKeys.LOOKUP_TABLE_COLUMN_SELECTION,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.lookupsGetQueryManager = new QueryManager({
|
||||
processQuery: async (query: string) => {
|
||||
this.lookupsQueryManager = new QueryManager({
|
||||
processQuery: async () => {
|
||||
const tiersResp = await axios.get('/druid/coordinator/v1/lookups/config?discover=true');
|
||||
const tiers =
|
||||
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 {
|
||||
this.lookupsGetQueryManager.terminate();
|
||||
this.lookupsQueryManager.terminate();
|
||||
}
|
||||
|
||||
private async initializeLookup() {
|
||||
try {
|
||||
await axios.post(`/druid/coordinator/v1/lookups/config`, {});
|
||||
this.lookupsGetQueryManager.rerunLastQuery();
|
||||
this.lookupsQueryManager.rerunLastQuery();
|
||||
} catch (e) {
|
||||
AppToaster.show({
|
||||
icon: IconNames.ERROR,
|
||||
|
@ -208,7 +207,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
|||
this.setState({
|
||||
lookupEditDialogOpen: false,
|
||||
});
|
||||
this.lookupsGetQueryManager.rerunLastQuery();
|
||||
this.lookupsQueryManager.rerunLastQuery();
|
||||
} catch (e) {
|
||||
AppToaster.show({
|
||||
icon: IconNames.ERROR,
|
||||
|
@ -254,7 +253,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
|||
intent={Intent.DANGER}
|
||||
onClose={success => {
|
||||
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>
|
||||
|
@ -296,28 +295,28 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
|||
{
|
||||
Header: 'Lookup name',
|
||||
id: 'lookup_name',
|
||||
accessor: (row: any) => row.id,
|
||||
accessor: 'id',
|
||||
filterable: true,
|
||||
show: hiddenColumns.exists('Lookup name'),
|
||||
},
|
||||
{
|
||||
Header: 'Tier',
|
||||
id: 'tier',
|
||||
accessor: (row: any) => row.tier,
|
||||
accessor: 'tier',
|
||||
filterable: true,
|
||||
show: hiddenColumns.exists('Tier'),
|
||||
},
|
||||
{
|
||||
Header: 'Type',
|
||||
id: 'type',
|
||||
accessor: (row: any) => row.spec.type,
|
||||
accessor: 'spec.type',
|
||||
filterable: true,
|
||||
show: hiddenColumns.exists('Type'),
|
||||
},
|
||||
{
|
||||
Header: 'Version',
|
||||
id: 'version',
|
||||
accessor: (row: any) => row.version,
|
||||
accessor: 'version',
|
||||
filterable: true,
|
||||
show: hiddenColumns.exists('Version'),
|
||||
},
|
||||
|
@ -325,7 +324,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
|||
Header: ActionCell.COLUMN_LABEL,
|
||||
id: ActionCell.COLUMN_ID,
|
||||
width: ActionCell.COLUMN_WIDTH,
|
||||
accessor: row => ({ id: row.id, tier: row.tier }),
|
||||
accessor: (row: any) => ({ id: row.id, tier: row.tier }),
|
||||
filterable: false,
|
||||
Cell: (row: any) => {
|
||||
const lookupId = row.value.id;
|
||||
|
@ -375,10 +374,9 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
|
|||
return (
|
||||
<div className="lookups-view app-view">
|
||||
<ViewControlBar label="Lookups">
|
||||
<Button
|
||||
icon={IconNames.REFRESH}
|
||||
text="Refresh"
|
||||
onClick={() => this.lookupsGetQueryManager.rerunLastQuery()}
|
||||
<RefreshButton
|
||||
onRefresh={() => this.lookupsQueryManager.rerunLastQuery()}
|
||||
localStorageKey={LocalStorageKeys.LOOKUPS_REFRESH_RATE}
|
||||
/>
|
||||
{!lookupsError && (
|
||||
<Button
|
||||
|
|
|
@ -26,7 +26,7 @@ import { ColumnMetadata } from '../../../utils/column-metadata';
|
|||
|
||||
import './column-tree.scss';
|
||||
|
||||
export interface ColumnTreeProps extends React.Props<any> {
|
||||
export interface ColumnTreeProps {
|
||||
columnMetadataLoading: boolean;
|
||||
columnMetadata: ColumnMetadata[] | null;
|
||||
onQueryStringChange: (queryString: string) => void;
|
||||
|
@ -150,11 +150,7 @@ export class ColumnTree extends React.PureComponent<ColumnTreeProps, ColumnTreeS
|
|||
);
|
||||
}
|
||||
|
||||
private handleNodeClick = (
|
||||
nodeData: ITreeNode,
|
||||
nodePath: number[],
|
||||
e: React.MouseEvent<HTMLElement>,
|
||||
) => {
|
||||
private handleNodeClick = (nodeData: ITreeNode, nodePath: number[]) => {
|
||||
const { onQueryStringChange } = this.props;
|
||||
const { columnTree, selectedTreeIndex } = this.state;
|
||||
if (!columnTree) return;
|
||||
|
|
|
@ -44,7 +44,7 @@ export interface QueryExtraInfoData {
|
|||
wrappedLimit?: number;
|
||||
}
|
||||
|
||||
export interface QueryExtraInfoProps extends React.Props<any> {
|
||||
export interface QueryExtraInfoProps {
|
||||
queryExtraInfo: QueryExtraInfoData;
|
||||
onDownload: (filename: string, format: string) => void;
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ import './query-input.scss';
|
|||
|
||||
const langTools = ace.acequire('ace/ext/language_tools');
|
||||
|
||||
export interface QueryInputProps extends React.Props<any> {
|
||||
export interface QueryInputProps {
|
||||
queryString: string;
|
||||
onQueryStringChange: (newQueryString: string) => void;
|
||||
runeMode: boolean;
|
||||
|
@ -71,7 +71,7 @@ export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputS
|
|||
);
|
||||
|
||||
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);
|
||||
},
|
||||
});
|
||||
|
@ -103,7 +103,7 @@ export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputS
|
|||
);
|
||||
|
||||
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);
|
||||
},
|
||||
};
|
||||
|
@ -137,7 +137,7 @@ export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputS
|
|||
});
|
||||
|
||||
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);
|
||||
},
|
||||
getDocTooltip: (item: any) => {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue