diff --git a/services/src/main/java/org/apache/druid/cli/RouterJettyServerInitializer.java b/services/src/main/java/org/apache/druid/cli/RouterJettyServerInitializer.java index b206e4b4b60..a336ae637f9 100644 --- a/services/src/main/java/org/apache/druid/cli/RouterJettyServerInitializer.java +++ b/services/src/main/java/org/apache/druid/cli/RouterJettyServerInitializer.java @@ -65,6 +65,7 @@ public class RouterJettyServerInitializer implements JettyServerInitializer protected static final List UNSECURED_PATHS_FOR_UI = ImmutableList.of( "/", "/coordinator-console/*", + "/assets/*", "/public/*", "/old-console/*", "/pages/*", diff --git a/web-console/package.json b/web-console/package.json index a4369d67e2c..edb85f2f61b 100644 --- a/web-console/package.json +++ b/web-console/package.json @@ -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)'", diff --git a/web-console/script/mkcomp b/web-console/script/mkcomp index 5c60a80af23..56349591c75 100755 --- a/web-console/script/mkcomp +++ b/web-console/script/mkcomp @@ -85,7 +85,7 @@ import React from 'react'; import './${name}.scss'; -export interface ${camelName}Props extends React.Props { +export interface ${camelName}Props { } export interface ${camelName}State { @@ -100,7 +100,7 @@ export class ${camelName} extends React.PureComponent<${camelName}Props, ${camel render() { return
- + Stuff...
; } } diff --git a/web-console/src/bootstrap/react-table-custom-pagination.tsx b/web-console/src/bootstrap/react-table-custom-pagination.tsx index 5c53b8e9da9..5be343df8ec 100644 --- a/web-console/src/bootstrap/react-table-custom-pagination.tsx +++ b/web-console/src/bootstrap/react-table-custom-pagination.tsx @@ -21,7 +21,7 @@ import React from 'react'; import './react-table-custom-pagination.scss'; -interface ReactTableCustomPaginationProps extends React.Props { +interface ReactTableCustomPaginationProps { pages: number; page: number; showPageSizeOptions: boolean; diff --git a/web-console/src/bootstrap/react-table-defaults.tsx b/web-console/src/bootstrap/react-table-defaults.tsx index 15c09cd19ec..f0d2b2ffa33 100644 --- a/web-console/src/bootstrap/react-table-defaults.tsx +++ b/web-console/src/bootstrap/react-table-defaults.tsx @@ -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]); }, diff --git a/web-console/src/components/action-cell/action-cell.tsx b/web-console/src/components/action-cell/action-cell.tsx index 0bd7e792040..32c2fa2ee2c 100644 --- a/web-console/src/components/action-cell/action-cell.tsx +++ b/web-console/src/components/action-cell/action-cell.tsx @@ -25,7 +25,7 @@ import { ActionIcon } from '../action-icon/action-icon'; import './action-cell.scss'; -export interface ActionCellProps extends React.Props { +export interface ActionCellProps { onDetail?: () => void; actions?: BasicAction[]; } diff --git a/web-console/src/components/action-icon/action-icon.tsx b/web-console/src/components/action-icon/action-icon.tsx index 36b4b4e61e5..c3c7da249f6 100644 --- a/web-console/src/components/action-icon/action-icon.tsx +++ b/web-console/src/components/action-icon/action-icon.tsx @@ -22,7 +22,7 @@ import React from 'react'; import './action-icon.scss'; -export interface ActionIconProps extends React.Props { +export interface ActionIconProps { className?: string; icon: IconName; onClick?: () => void; diff --git a/web-console/src/components/auto-form/__snapshots__/auto-form.spec.tsx.snap b/web-console/src/components/auto-form/__snapshots__/auto-form.spec.tsx.snap index df8004adec1..b95c63c871c 100644 --- a/web-console/src/components/auto-form/__snapshots__/auto-form.spec.tsx.snap +++ b/web-console/src/components/auto-form/__snapshots__/auto-form.spec.tsx.snap @@ -190,7 +190,7 @@ exports[`auto-form snapshot matches snapshot 1`] = ` class="bp3-form-content" >
{ { name: 'testSeven', type: 'json' }, ]} model={String} - onChange={(newModel: Record) => {}} + onChange={() => {}} /> ); const { container } = render(autoForm); diff --git a/web-console/src/components/auto-form/auto-form.tsx b/web-console/src/components/auto-form/auto-form.tsx index 85ee1de1153..e7db35781ee 100644 --- a/web-console/src/components/auto-form/auto-form.tsx +++ b/web-console/src/components/auto-form/auto-form.tsx @@ -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 { name: string; label?: string; @@ -55,7 +40,7 @@ export interface Field { min?: number; } -export interface AutoFormProps extends React.Props { +export interface AutoFormProps { fields: Field[]; model: T | null; onChange: (newModel: T) => void; @@ -64,13 +49,13 @@ export interface AutoFormProps extends React.Props { large?: boolean; } -export interface AutoFormState { +export interface AutoFormState { jsonInputsValidity: any; } export class AutoForm> extends React.PureComponent< AutoFormProps, - AutoFormState + AutoFormState > { static makeLabelName(label: string): string { let newLabel = label @@ -159,42 +144,11 @@ export class AutoForm> extends React.PureComponent private renderStringInput(field: Field, sanitize?: (str: string) => string): JSX.Element { const { model, large } = this.props; - const suggestionsMenu = field.suggestions ? ( - - {field.suggestions.map(suggestion => { - if (typeof suggestion === 'string') { - return ( - this.fieldChange(field, suggestion)} - /> - ); - } else { - return ( - - {suggestion.suggestions.map(suggestion => ( - this.fieldChange(field, suggestion)} - /> - ))} - - ); - } - })} - - ) : ( - undefined - ); - const modalValue = deepGet(model as any, field.name); return ( - { - let v = e.target.value; + onValueChange={v => { if (sanitize) v = sanitize(v); this.fieldChange(field, v); }} @@ -202,13 +156,7 @@ export class AutoForm> extends React.PureComponent if (modalValue === '') this.fieldChange(field, undefined); }} placeholder={field.placeholder} - rightElement={ - suggestionsMenu && ( - - + + + +
+`; diff --git a/web-console/src/dialogs/overlord-dynamic-config/overload-dynamic-config.spec.tsx b/web-console/src/components/suggestible-input/suggestible-input.spec.tsx similarity index 62% rename from web-console/src/dialogs/overlord-dynamic-config/overload-dynamic-config.spec.tsx rename to web-console/src/components/suggestible-input/suggestible-input.spec.tsx index f9d635b5460..ddc48c15f2b 100644 --- a/web-console/src/dialogs/overlord-dynamic-config/overload-dynamic-config.spec.tsx +++ b/web-console/src/components/suggestible-input/suggestible-input.spec.tsx @@ -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 = ( - 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 = ( + {}} suggestions={['a', 'b', 'c']} /> ); - const { container } = render(lookupEditDialog, { container: document.body }); + + const { container } = render(suggestibleInput); expect(container.firstChild).toMatchSnapshot(); }); }); diff --git a/web-console/src/components/suggestible-input/suggestible-input.tsx b/web-console/src/components/suggestible-input/suggestible-input.tsx new file mode 100644 index 00000000000..47eb17446d0 --- /dev/null +++ b/web-console/src/components/suggestible-input/suggestible-input.tsx @@ -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 { + constructor(props: SuggestibleInputProps, context: any) { + super(props, context); + // this.state = {}; + } + + renderSuggestionsMenu() { + const { suggestions, onValueChange } = this.props; + if (!suggestions) return undefined; + + return ( + + {suggestions.map(suggestion => { + if (typeof suggestion === 'string') { + return ( + onValueChange(suggestion)} + /> + ); + } else { + return ( + + {suggestion.suggestions.map(suggestion => ( + onValueChange(suggestion)} + /> + ))} + + ); + } + })} + + ); + } + + render() { + const { className, value, defaultValue, onValueChange, large, ...rest } = this.props; + const suggestionsMenu = this.renderSuggestionsMenu(); + + return ( + { + onValueChange(e.target.value); + }} + rightElement={ + suggestionsMenu && ( + + + +
+

+ Edit the overlord dynamic configuration on the fly. For more information please refer to the + + + documentation + + . +

+
+
+ +
+ + + +`; diff --git a/web-console/src/dialogs/overlord-dynamic-config-dialog/overload-dynamic-config-dialog.spec.tsx b/web-console/src/dialogs/overlord-dynamic-config-dialog/overload-dynamic-config-dialog.spec.tsx new file mode 100644 index 00000000000..8a2011d48f4 --- /dev/null +++ b/web-console/src/dialogs/overlord-dynamic-config-dialog/overload-dynamic-config-dialog.spec.tsx @@ -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 = null} />; + + render(lookupEditDialog); + expect(document.body.lastChild).toMatchSnapshot(); + }); +}); diff --git a/web-console/src/dialogs/overlord-dynamic-config/overlord-dynamic-config.scss b/web-console/src/dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog.scss similarity index 97% rename from web-console/src/dialogs/overlord-dynamic-config/overlord-dynamic-config.scss rename to web-console/src/dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog.scss index 6e676ad683f..f655cc92e46 100644 --- a/web-console/src/dialogs/overlord-dynamic-config/overlord-dynamic-config.scss +++ b/web-console/src/dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog.scss @@ -16,7 +16,7 @@ * limitations under the License. */ -.overlord-dynamic-config { +.overlord-dynamic-config-dialog { &.bp3-dialog { margin-top: 5vh; top: 5%; diff --git a/web-console/src/dialogs/overlord-dynamic-config/overlord-dynamic-config.tsx b/web-console/src/dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog.tsx similarity index 94% rename from web-console/src/dialogs/overlord-dynamic-config/overlord-dynamic-config.tsx rename to web-console/src/dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog.tsx index 81ee2f68ddc..dcf94233f25 100644 --- a/web-console/src/dialogs/overlord-dynamic-config/overlord-dynamic-config.tsx +++ b/web-console/src/dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog.tsx @@ -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 { +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 ( -
-
-
-
-
-

- Add lookup -

- -
-
- -
-
- -
-
-
-
- -
-
- - - - - double-caret-vertical - - - - -
-
-
-
- -
-
- - - - -
-
-
-
- -
-
-
-