Web console: Switch to ESLint (#11142)

* Initial eslint config

* I guess eslint sorts underscores differently

* Trim curlies (in jsx)

* Re-organize rules

* Use consistent quote props

* Restructure eslint rules as additions/overrides to recommended configs

* Fix the 'recommended' stuff

* Add prefer-readonly

* Add prefer-object-spread

* Prettify

* Add eslint-plugin-react-hooks

* Switch to eslint-plugin-simple-sort-order

So much better

* Add no-extraneous-dependencies

* ban-tslint-comment for funzies

* If we enabled no-shadow, we'd probably want this option

* Not prefer-for-of

* no-confusing-void-expression, no-confusing-non-null-assertion

* Add some no-unnecessary-* rules

* non-nullable-type-assertion-style!

* prefer-includes

* Reorganize

* prefer-things

* switch-exhaustiveness-check

* We don't need the jsdoc plugin, prettier has our backs

* Remove a useless rule

* Drop TSLint and (temporarily) awesome-code-style

* Removing Object.assign revealed a type issue

* Bring back awesome-code-style for sasslint config

* Disable react/jsx-no-target-blank

* Add prettify-check script

* Add license to eslint config

* Format readme

* Update README for eslint, IDE settings

* Add 'autofix' script

* Switch to @awesome-code-style
This commit is contained in:
John Gozde 2021-04-22 20:33:03 -06:00 committed by GitHub
parent 57ff1f9cdb
commit 9745d9e1c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
138 changed files with 2561 additions and 875 deletions

View File

@ -16,14 +16,24 @@
* limitations under the License.
*/
import { useEffect } from 'react';
export function useRenderSpy(componentName: string, props: Record<string, any>) {
console.log(`Render on ${componentName}`);
const propKeys: string[] = Object.keys(props).sort();
for (const key of propKeys) {
useEffect(() => {
console.log(`${key} changed`);
}, [(props as any)[key]]);
}
}
module.exports = {
env: {
browser: true,
},
extends: ['@awesome-code-style'],
parserOptions: {
project: 'tsconfig.json',
},
settings: {
react: {
version: 'detect',
},
},
rules: {
'header/header': [
2,
'block',
{ pattern: 'Licensed to the Apache Software Foundation \\(ASF\\).+' },
],
},
};

View File

@ -1,17 +1,4 @@
{
"plugins": [
"stylelint-scss"
],
"defaultSeverity": "error",
"extends": "stylelint-config-recommended-scss",
"rules": {
"indentation": 2,
"at-rule-no-unknown": null,
"scss/at-rule-no-unknown": true,
"scss/at-import-no-partial-leading-underscore": true,
"scss/dollar-variable-colon-space-after": "always",
"scss/dollar-variable-colon-space-before": "never",
"no-descending-specificity": null,
"no-missing-end-of-source-newline": true
}
"extends": "@awesome-code-style/stylelint-config",
"rules": {}
}

View File

@ -30,7 +30,6 @@ This is the Druid web console that servers as a data management interface for Dr
3. Run `npm run compile` to compile the scss files (this usually needs to be done only once)
4. Run `npm start` will start in development mode and will proxy druid requests to `localhost:8888`
**Note:** you can provide an environment variable to proxy to a different Druid host like so: `druid_host=1.2.3.4:8888 npm start`
**Note:** you can provide an environment variable use webpack-bundle-analyzer as a plugin in the build script or like so: `BUNDLE_ANALYZER_PLUGIN='TRUE' npm start`
@ -42,18 +41,41 @@ To try the console in (say) coordinator mode you could run it as such:
You should use a TypeScript friendly IDE (such as [WebStorm](https://www.jetbrains.com/webstorm/), or [VS Code](https://code.visualstudio.com/)) to develop the web console.
The console relies on [tslint](https://palantir.github.io/tslint/), [sass-lint](https://github.com/sasstools/sass-lint), and [prettier](https://prettier.io/) to enforce the code style.
The console relies on [eslint](https://eslint.org) (and various plugins), [sass-lint](https://github.com/sasstools/sass-lint), and [prettier](https://prettier.io/) to enforce code style. If you are going to do any non-trivial development you should set up your IDE to automatically lint and fix your code as you make changes.
If you are going to do any non-trivial development you should set up file watchers in your IDE to automatically fix your code as you type.
#### Configuring WebStorm
If you do not set up auto file watchers then even a trivial change such as a typo fix might draw the ire of the code style enforcement (it might require some lines to be re-wrapped).
If you find yourself in that position you should run on or more of:
- **Preferences | Languages & Frameworks | JavaScript | Code Quality Tools | ESLint**
- Select "Automatic ESLint Configuration"
- Check "Run eslint --fix on save"
- `npm run tslint-fix`
- `npm run sasslint-fix`
- `npm run prettify`
- **Preferences | Languages & Frameworks | JavaScript | Prettier**
- Set "Run for files" to `{**/*,*}.{js,ts,jsx,tsx,css,scss}`
- Check "On code reformat"
- Check "On save"
To get your code into an acceptable state.
#### Configuring VS Code
- Install `dbaeumer.vscode-eslint` extension
- Install `esbenp.prettier-vscode` extension
- Open User Settings (JSON) and set the following:
```json
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
```
#### Auto-fixing manually
It is also possible to auto-fix and format code without making IDE changes by running the following script:
- `npm run autofix` &mdash; run code linters and formatter
You could also run fixers individually:
- `npm run eslint-fix` &mdash; run code linter and fix issues
- `npm run sasslint-fix` &mdash; run style linter and fix issues
- `npm run prettify` &mdash; reformat code and styles
### Updating the list of license files
@ -93,7 +115,6 @@ The environment variable `DRUID_E2E_TEST_UNIFIED_CONSOLE_PORT` can be used to ta
non-default port (i.e., not port `8888`). For example, this environment variable can be used to target the
development mode of the web console (started via `npm start`), which runs on port `18081`.
## Description of the directory structure
As part of this directory:

View File

@ -23,19 +23,19 @@ module.exports = function (api) {
[
'@babel/preset-env',
{
"useBuiltIns": "entry",
"corejs": 3,
"forceAllTransforms": true,
"targets": {
"ie": "11"
}
}
]
useBuiltIns: 'entry',
corejs: 3,
forceAllTransforms: true,
targets: {
ie: '11',
},
},
],
];
const plugins = [];
return {
presets,
plugins
plugins,
};
}
};

View File

@ -17,6 +17,6 @@
*/
window.consoleConfig = {
"exampleManifestsUrl": "https://druid.apache.org/data/example-manifests-v2.tsv"
exampleManifestsUrl: 'https://druid.apache.org/data/example-manifests-v2.tsv',
/* future configs may go here */
};

View File

@ -24,11 +24,12 @@ import { Datasource } from './component/datasources/datasource';
import { DatasourcesOverview } from './component/datasources/overview';
import { HashedPartitionsSpec } from './component/load-data/config/partition';
import { saveScreenshotIfError } from './util/debug';
import { DRUID_EXAMPLES_QUICKSTART_TUTORIAL_DIR } from './util/druid';
import { UNIFIED_CONSOLE_URL } from './util/druid';
import { runIndexTask } from './util/druid';
import { createBrowser } from './util/playwright';
import { createPage } from './util/playwright';
import {
DRUID_EXAMPLES_QUICKSTART_TUTORIAL_DIR,
runIndexTask,
UNIFIED_CONSOLE_URL,
} from './util/druid';
import { createBrowser, createPage } from './util/playwright';
import { retryIfJestAssertionError } from './util/retry';
import { waitTillWebConsoleReady } from './util/setup';
@ -154,6 +155,6 @@ async function waitForCompaction(
async function getNumSegment(page: playwright.Page, datasourceName: string): Promise<number> {
const datasource = await getDatasource(page, datasourceName);
const currNumSegmentString = datasource!.availability.match(/(\d+)/)![0];
const currNumSegmentString = /(\d+)/.exec(datasource.availability)![0];
return Number(currNumSegmentString);
}

View File

@ -124,7 +124,7 @@ export class DatasourcesOverview {
}
const editActions = await this.page.$$('span[icon=wrench]');
editActions[index].click();
await editActions[index].click();
await this.waitForPopupMenu();
}

View File

@ -16,12 +16,12 @@
* limitations under the License.
*/
/* eslint-disable max-classes-per-file */
import * as playwright from 'playwright-chromium';
import { getLabeledInput, selectSuggestibleInput, setLabeledInput } from '../../../util/playwright';
/* tslint:disable max-classes-per-file */
/**
* Possible values for partition step segment granularity.
*/
@ -52,7 +52,7 @@ export async function readPartitionSpec(page: playwright.Page): Promise<Partitio
export class HashedPartitionsSpec implements PartitionsSpec {
public static TYPE = 'hashed';
private static NUM_SHARDS = 'Num shards';
private static readonly NUM_SHARDS = 'Num shards';
readonly type: string;
@ -90,9 +90,9 @@ export interface HashedPartitionsSpec extends HashedPartitionsSpecProps {}
export class SingleDimPartitionsSpec implements PartitionsSpec {
public static TYPE = 'single_dim';
private static PARTITION_DIMENSION = 'Partition dimension';
private static TARGET_ROWS_PER_SEGMENT = 'Target rows per segment';
private static MAX_ROWS_PER_SEGMENT = 'Max rows per segment';
private static readonly PARTITION_DIMENSION = 'Partition dimension';
private static readonly TARGET_ROWS_PER_SEGMENT = 'Target rows per segment';
private static readonly MAX_ROWS_PER_SEGMENT = 'Max rows per segment';
readonly type: string;

View File

@ -20,8 +20,7 @@ import * as playwright from 'playwright-chromium';
import { setLabeledInput } from '../../../util/playwright';
import { clickApplyButton } from './data-connector';
import { DataConnector } from './data-connector';
import { clickApplyButton, DataConnector } from './data-connector';
/**
* Local file connector for data loader input data.
@ -29,7 +28,7 @@ import { DataConnector } from './data-connector';
export class LocalFileDataConnector implements DataConnector {
readonly name: string;
readonly needParse: boolean;
private page: playwright.Page;
private readonly page: playwright.Page;
constructor(page: playwright.Page, props: LocalFileDataConnectorProps) {
Object.assign(this, props);

View File

@ -20,8 +20,7 @@ import * as playwright from 'playwright-chromium';
import { setLabeledInput } from '../../../util/playwright';
import { clickApplyButton } from './data-connector';
import { DataConnector } from './data-connector';
import { clickApplyButton, DataConnector } from './data-connector';
/**
* Reindexing connector for data loader input data.
@ -29,7 +28,7 @@ import { DataConnector } from './data-connector';
export class ReindexDataConnector implements DataConnector {
readonly name: string;
readonly needParse: boolean;
private page: playwright.Page;
private readonly page: playwright.Page;
constructor(page: playwright.Page, props: ReindexDataConnectorProps) {
Object.assign(this, props);

View File

@ -34,7 +34,7 @@ export class DataLoader {
constructor(props: DataLoaderProps) {
Object.assign(this, props);
this.baseUrl = props.unifiedConsoleUrl! + '#load-data';
this.baseUrl = props.unifiedConsoleUrl + '#load-data';
}
/**
@ -74,7 +74,7 @@ export class DataLoader {
const previewSelector = '.raw-lines';
await this.page.waitForSelector(previewSelector);
const preview = await this.page.$eval(previewSelector, el => (el as HTMLTextAreaElement).value);
validator(preview!);
validator(preview);
}
private async parseData() {

View File

@ -22,18 +22,21 @@ import * as playwright from 'playwright-chromium';
import { DatasourcesOverview } from './component/datasources/overview';
import { IngestionOverview } from './component/ingestion/overview';
import { ConfigureSchemaConfig } from './component/load-data/config/configure-schema';
import { PartitionConfig } from './component/load-data/config/partition';
import { SegmentGranularity } from './component/load-data/config/partition';
import { SingleDimPartitionsSpec } from './component/load-data/config/partition';
import {
PartitionConfig,
SegmentGranularity,
SingleDimPartitionsSpec,
} from './component/load-data/config/partition';
import { PublishConfig } from './component/load-data/config/publish';
import { ReindexDataConnector } from './component/load-data/data-connector/reindex';
import { DataLoader } from './component/load-data/data-loader';
import { saveScreenshotIfError } from './util/debug';
import { DRUID_EXAMPLES_QUICKSTART_TUTORIAL_DIR } from './util/druid';
import { UNIFIED_CONSOLE_URL } from './util/druid';
import { runIndexTask } from './util/druid';
import { createBrowser } from './util/playwright';
import { createPage } from './util/playwright';
import {
DRUID_EXAMPLES_QUICKSTART_TUTORIAL_DIR,
runIndexTask,
UNIFIED_CONSOLE_URL,
} from './util/druid';
import { createBrowser, createPage } from './util/playwright';
import { retryIfJestAssertionError } from './util/retry';
import { waitTillWebConsoleReady } from './util/setup';

View File

@ -26,6 +26,7 @@ export async function retryIfJestAssertionError(
maxTries = 60,
) {
let i = 0;
// eslint-disable-next-line no-constant-condition
while (true) {
try {
await callback();

View File

@ -17,8 +17,7 @@
*/
import { UNIFIED_CONSOLE_URL } from './druid';
import { createBrowser } from './playwright';
import { createPage } from './playwright';
import { createBrowser, createPage } from './playwright';
export async function waitTillWebConsoleReady() {
const browser = await createBrowser();

View File

@ -17,11 +17,11 @@
*/
module.exports = {
"preset": "ts-jest",
"globals": {
"ts-jest": {
"tsconfig": "./tsconfig.test.json"
}
preset: 'ts-jest',
globals: {
'ts-jest': {
tsconfig: './tsconfig.test.json',
},
"testEnvironment": "jsdom",
},
testEnvironment: 'jsdom',
};

View File

@ -19,7 +19,5 @@
const common = require('./jest.common.config');
module.exports = Object.assign(common, {
"testMatch": [
"**/?(*.)+(spec).ts?(x)"
]
testMatch: ['**/?(*.)+(spec).ts?(x)'],
});

View File

@ -19,16 +19,10 @@
const common = require('./jest.common.config');
module.exports = Object.assign(common, {
"moduleNameMapper": {
"\\.s?css$": "identity-obj-proxy"
moduleNameMapper: {
'\\.s?css$': 'identity-obj-proxy',
},
"snapshotSerializers": [
"enzyme-to-json/serializer"
],
"setupFilesAfterEnv": [
"<rootDir>src/setup-tests.ts"
],
"testMatch": [
"**/src/**/?(*.)+(spec).(ts|tsx)"
],
snapshotSerializers: ['enzyme-to-json/serializer'],
setupFilesAfterEnv: ['<rootDir>src/setup-tests.ts'],
testMatch: ['**/src/**/?(*.)+(spec).(ts|tsx)'],
});

File diff suppressed because it is too large Load Diff

View File

@ -27,35 +27,29 @@
"<rootDir>src/setup-tests.ts"
]
},
"prettier": {
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"printWidth": 100,
"endOfLine": "lf",
"arrowParens": "avoid"
},
"prettier": "@awesome-code-style/prettier-config",
"scripts": {
"compile": "./script/build",
"pretest": "./script/build",
"jest": "jest --config jest.unit.config.js src",
"test-base": "npm run tslint && npm run sasslint && npm run jest",
"test-base": "npm run eslint && npm run sasslint && npm run prettify-check && npm run jest",
"test": "npm run test-base -- --silent 2>&1",
"test-ci": "npm run test-base -- --coverage",
"test-e2e": "jest --config jest.e2e.config.js e2e-tests",
"codecov": "codecov --disable=gcov -p ..",
"coverage": "jest --coverage src",
"update-snapshots": "jest -u",
"tslint": "./node_modules/.bin/tslint -c tslint.json --project tsconfig.json --formatters-dir ./node_modules/awesome-code-style/formatter '{src,e2e-tests}/**/*.ts?(x)'",
"tslint-fix": "npm run tslint -- --fix",
"tslint-changed-only": "git diff --diff-filter=ACMR --cached --name-only | grep -E \\.tsx\\?$ | xargs ./node_modules/.bin/tslint -c tslint.json --project tsconfig.json --formatters-dir ./node_modules/awesome-code-style/formatter",
"tslint-fix-changed-only": "npm run tslint-changed-only -- --fix",
"sasslint": "./node_modules/.bin/stylelint --config sasslint.json 'src/**/*.scss'",
"autofix": "npm run eslint-fix && npm run sasslint-fix && npm run prettify",
"eslint": "eslint '{src,e2e-tests}/**/*.ts?(x)'",
"eslint-fix": "npm run eslint -- --fix",
"eslint-changed-only": "git diff --diff-filter=ACMR --cached --name-only | grep -E \\.tsx\\?$ | xargs ./node_modules/.bin/eslint",
"eslint-fix-changed-only": "npm run eslint-changed-only -- --fix",
"sasslint": "./node_modules/.bin/stylelint 'src/**/*.scss'",
"sasslint-fix": "npm run sasslint -- --fix",
"sasslint-changed-only": "git diff --diff-filter=ACMR --name-only | grep -E \\.scss$ | xargs ./node_modules/.bin/stylelint --config sasslint.json",
"sasslint-changed-only": "git diff --diff-filter=ACMR --name-only | grep -E \\.scss$ | xargs ./node_modules/.bin/stylelint",
"sasslint-fix-changed-only": "npm run sasslint-changed-only -- --fix",
"prettify": "prettier --write '{src,e2e-tests}/**/*.{ts,tsx,scss}'",
"prettify": "prettier --write '{src,e2e-tests}/**/*.{ts,tsx,scss}' './*.js'",
"prettify-check": "prettier --check '{src,e2e-tests}/**/*.{ts,tsx,scss}' './*.js'",
"generate-licenses-file": "license-checker --production --json --out licenses.json",
"check-licenses": "license-checker --production --onlyAllow 'Apache-1.1;Apache-2.0;BSD-2-Clause;BSD-3-Clause;0BSD;MIT;CC0-1.0' --summary",
"start": "webpack serve --hot --open"
@ -94,6 +88,9 @@
"tslib": "^2.2.0"
},
"devDependencies": {
"@awesome-code-style/eslint-config": "^3.0.0",
"@awesome-code-style/prettier-config": "^3.0.0",
"@awesome-code-style/stylelint-config": "^3.0.0",
"@babel/core": "^7.13.15",
"@babel/preset-env": "^7.13.15",
"@testing-library/react": "^8.0.9",
@ -118,14 +115,22 @@
"@types/react-splitter-layout": "^3.0.0",
"@types/react-table": "6.8.5",
"@types/uuid": "^7.0.2",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"autoprefixer": "^9.8.6",
"awesome-code-style": "^2.1.0",
"babel-loader": "^8.2.2",
"codecov": "^3.6.1",
"css-loader": "^5.2.1",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.15.1",
"enzyme-to-json": "^3.4.3",
"eslint": "^7.24.0",
"eslint-config-prettier": "^8.2.0",
"eslint-plugin-header": "^3.1.1",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-react": "^7.23.2",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-simple-import-sort": "^7.0.0",
"eslint-plugin-unicorn": "^30.0.0",
"file-loader": "^6.2.0",
"fs-extra": "^8.1.0",
"identity-obj-proxy": "^3.0.0",
@ -140,14 +145,10 @@
"sass-loader": "^11.0.1",
"style-loader": "^2.0.0",
"stylelint": "^13.12.0",
"stylelint-config-recommended-scss": "^4.2.0",
"stylelint-scss": "^3.19.0",
"stylus": "^0.54.7",
"ts-jest": "^26.5.5",
"ts-loader": "^8.1.0",
"ts-node": "^8.4.1",
"tslint": "^6.1.3",
"tslint-loader": "^3.5.4",
"typescript": "^4.2.4",
"uuid": "^7.0.2",
"webpack": "^5.33.2",

View File

@ -1,4 +0,0 @@
{
"extends": "awesome-code-style/sasslint.json",
"rules": {}
}

View File

@ -19,6 +19,5 @@
import 'brace'; // Import Ace editor and all the sub components used in the app
import 'brace/ext/language_tools';
import 'brace/theme/solarized_dark';
import '../ace-modes/dsql';
import '../ace-modes/hjson';

View File

@ -41,7 +41,7 @@ interface ReactTableCustomPaginationProps {
}
interface ReactTableCustomPaginationState {
page: string | number;
page: '' | number;
}
export class ReactTableCustomPagination extends React.PureComponent<
@ -56,8 +56,10 @@ export class ReactTableCustomPagination extends React.PureComponent<
};
}
componentWillReceiveProps(nextProps: ReactTableCustomPaginationProps) {
this.setState({ page: nextProps.page });
static getDerivedStateFromProps(nextProps: ReactTableCustomPaginationProps) {
return {
page: nextProps.page,
};
}
getSafePage = (page: any) => {
@ -126,13 +128,11 @@ export class ReactTableCustomPagination extends React.PureComponent<
type={this.state.page === '' ? 'text' : 'number'}
onChange={e => {
const val: string = e.target.value;
const page: number = parseInt(val, 10) - 1;
if (val === '') {
return this.setState({ page: val });
}
this.setState({ page: this.getSafePage(page) });
this.setState({
page: val === '' ? val : this.getSafePage(parseInt(val, 10) - 1),
});
}}
value={this.state.page === '' ? '' : (this.state.page as number) + 1}
value={this.state.page === '' ? '' : this.state.page + 1}
onBlur={this.applyPage}
onKeyPress={e => {
if (e.which === 13 || e.keyCode === 13) {

View File

@ -42,7 +42,7 @@ export function bootstrapReactTable() {
NoDataComponent: NoData,
FilterComponent: makeTextFilter(),
PaginationComponent: ReactTableCustomPagination,
AggregatedComponent: (opt: any) => {
AggregatedComponent: function Aggregated(opt: any) {
const { subRows, column } = opt;
const previewValues = subRows
.filter((d: any) => typeof d[column.id] !== 'undefined')

View File

@ -26,8 +26,8 @@ describe('array input', () => {
const arrayInput = (
<ArrayInput
values={['apple', 'banana', 'pear']}
className={'test'}
placeholder={'test'}
className="test"
placeholder="test"
onChange={() => {}}
/>
);

View File

@ -33,7 +33,7 @@ export interface ArrayInputProps {
export const ArrayInput = React.memo(function ArrayInput(props: ArrayInputProps) {
const { className, placeholder, large, disabled, intent } = props;
const [stringValue, setStringValue] = useState();
const [stringValue, setStringValue] = useState<string>();
const handleChange = (e: any) => {
const { onChange } = props;
@ -54,7 +54,7 @@ export const ArrayInput = React.memo(function ArrayInput(props: ArrayInputProps)
return (
<TextArea
className={className}
value={stringValue || (props.values || []).join(', ')}
value={stringValue ?? props.values?.join(', ') ?? ''}
onChange={handleChange}
placeholder={placeholder}
large={large}

View File

@ -155,7 +155,7 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
};
}
private fieldChange = (field: Field<T>, newValue: any) => {
private readonly fieldChange = (field: Field<T>, newValue: any) => {
const { model } = this.props;
if (!model) return;
@ -181,7 +181,7 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
this.modelChange(newModel);
};
private modelChange = (newModel: T) => {
private readonly modelChange = (newModel: T) => {
const { globalAdjustment, fields, onChange, model } = this.props;
// Delete things that are not defined now (but were defined prior to the change)
@ -406,7 +406,7 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
}
}
private renderField = (field: Field<T>) => {
private readonly renderField = (field: Field<T>) => {
const { model } = this.props;
if (!model) return;

View File

@ -25,9 +25,9 @@ describe('clearable-input', () => {
it('matches snapshot', () => {
const centerMessage = (
<ClearableInput
className={'testClassName'}
value={'testValue'}
placeholder={'testPlaceholder'}
className="testClassName"
value="testValue"
placeholder="testPlaceholder"
onChange={() => {}}
/>
);

View File

@ -32,7 +32,7 @@ jest.mock('../../hooks', () => {
describe('DatasourceColumnsTable', () => {
function makeDatasourceColumnsTable() {
return <DatasourceColumnsTable datasourceId={'test'} downloadFilename={'test'} />;
return <DatasourceColumnsTable datasourceId="test" downloadFilename="test" />;
}
it('matches snapshot on init', () => {

View File

@ -24,7 +24,7 @@ import { ExternalLink } from './external-link';
describe('external link', () => {
it('matches snapshot', () => {
const externalLink = (
<ExternalLink href={'http://test/'}>
<ExternalLink href="http://test/">
<div>hello world</div>
</ExternalLink>
);

View File

@ -35,7 +35,7 @@ export const FormGroupWithInfo = React.memo(function FormGroupWithInfo(
const { label, info, inlineInfo, children } = props;
const popover = (
<Popover content={info} position="left-bottom" boundary={'viewport'}>
<Popover content={info} position="left-bottom" boundary="viewport">
<Icon icon={IconNames.INFO_SIGN} iconSize={14} />
</Popover>
);

View File

@ -25,7 +25,7 @@ import { HeaderBar } from './header-bar';
describe('header bar', () => {
it('matches snapshot', () => {
const headerBar = shallow(<HeaderBar active={'load-data'} capabilities={Capabilities.FULL} />);
const headerBar = shallow(<HeaderBar active="load-data" capabilities={Capabilities.FULL} />);
expect(headerBar).toMatchSnapshot();
});
});

View File

@ -24,6 +24,7 @@ export * from './braced-text/braced-text';
export * from './center-message/center-message';
export * from './clearable-input/clearable-input';
export * from './external-link/external-link';
export * from './form-json-selector/form-json-selector';
export * from './header-bar/header-bar';
export * from './highlight-text/highlight-text';
export * from './json-collapse/json-collapse';
@ -41,4 +42,3 @@ export * from './table-cell/table-cell';
export * from './table-column-selector/table-column-selector';
export * from './timed-button/timed-button';
export * from './view-control-bar/view-control-bar';
export * from './form-json-selector/form-json-selector';

View File

@ -26,8 +26,8 @@ describe('interval calendar component', () => {
it('matches snapshot', () => {
const intervalInput = (
<IntervalInput
interval={'2010-01-01/2020-01-01'}
placeholder={'2010-01-01/2020-01-01'}
interval="2010-01-01/2020-01-01"
placeholder="2010-01-01/2020-01-01"
onValueChange={() => {}}
intent={Intent.PRIMARY}
/>

View File

@ -69,10 +69,10 @@ export const IntervalInput = React.memo(function IntervalInput(props: IntervalIn
rightElement={
<div>
<Popover
popoverClassName={'calendar'}
popoverClassName="calendar"
content={
<DateRangePicker
timePrecision={'second'}
timePrecision="second"
value={parseInterval(interval)}
contiguousCalendarMonths={false}
onChange={(selectedRange: DateRange) => {

View File

@ -25,7 +25,7 @@ import { JsonCollapse } from './json-collapse';
describe('JsonCollapse', () => {
it('matches snapshot', () => {
const jsonCollapse = shallow(
<JsonCollapse buttonText={'test'} stringValue={JSONBig.stringify({ name: 'test' })} />,
<JsonCollapse buttonText="test" stringValue={JSONBig.stringify({ name: 'test' })} />,
);
expect(jsonCollapse).toMatchSnapshot();
});

View File

@ -37,7 +37,7 @@ export function extractRowColumnFromHjsonError(
// Message would be something like:
// `Found '}' where a key name was expected at line 26,7`
// Use this to extract the row and column (subtract 1) and jump the cursor to the right place on click
const m = error.message.match(/line (\d+),(\d+)/);
const m = /line (\d+),(\d+)/.exec(error.message);
if (!m) return;
return { row: Number(m[1]) - 1, column: Number(m[2]) - 1 };
@ -89,7 +89,7 @@ export const JsonInput = React.memo(function JsonInput(props: JsonInputProps) {
value,
stringified: stringifyJson(value),
});
}, [value]);
}, [value]); // eslint-disable-line react-hooks/exhaustive-deps
const internalValueError = internalValue.error;
return (

View File

@ -23,7 +23,7 @@ import { Loader } from './loader';
describe('loader', () => {
it('matches snapshot', () => {
const loader = <Loader loadingText={'test'} />;
const loader = <Loader loadingText="test" />;
const { container } = render(loader);
expect(container.firstChild).toMatchSnapshot();
});

View File

@ -23,7 +23,7 @@ import { LookupValuesTable } from './lookup-values-table';
describe('rule editor', () => {
it('matches snapshot', () => {
const showJson = <LookupValuesTable lookupId={'test'} downloadFilename={'test'} />;
const showJson = <LookupValuesTable lookupId="test" downloadFilename="test" />;
const { container } = render(showJson);
expect(container.firstChild).toMatchSnapshot();
});

View File

@ -37,13 +37,11 @@ export const NumericInputWithDefault = React.memo(function NumericInputWithDefau
value={effectiveValue}
onValueChange={(valueAsNumber, valueAsString, inputElement) => {
setHasChanged(true);
if (!onValueChange) return;
return onValueChange(valueAsNumber, valueAsString, inputElement);
onValueChange?.(valueAsNumber, valueAsString, inputElement);
}}
onBlur={e => {
setHasChanged(false);
if (!onBlur) return;
return onBlur(e);
onBlur?.(e);
}}
{...rest}
/>

View File

@ -51,10 +51,10 @@ export const RuleEditor = React.memo(function RuleEditor(props: RuleEditorProps)
const [isOpen, setIsOpen] = useState(true);
function removeTier(key: string) {
const newTierReplicants = Object.assign({}, rule.tieredReplicants);
const newTierReplicants = { ...rule.tieredReplicants };
delete newTierReplicants[key];
const newRule = Object.assign({}, rule, { tieredReplicants: newTierReplicants });
const newRule = { ...rule, tieredReplicants: newTierReplicants };
onChange(newRule);
}
@ -162,9 +162,7 @@ export const RuleEditor = React.memo(function RuleEditor(props: RuleEditorProps)
<ControlGroup>
<HTMLSelect
value={rule.type}
onChange={(e: any) =>
onChange(RuleUtil.changeRuleType(rule, e.target.value as any))
}
onChange={(e: any) => onChange(RuleUtil.changeRuleType(rule, e.target.value))}
>
{RuleUtil.TYPES.map(type => {
return (
@ -200,9 +198,7 @@ export const RuleEditor = React.memo(function RuleEditor(props: RuleEditorProps)
{RuleUtil.hasInterval(rule) && (
<InputGroup
value={rule.interval || ''}
onChange={(e: any) =>
onChange(RuleUtil.changeInterval(rule, e.target.value as any))
}
onChange={(e: any) => onChange(RuleUtil.changeInterval(rule, e.target.value))}
placeholder="2010-01-01/2020-01-01"
/>
)}

View File

@ -20,8 +20,7 @@ import { render } from '@testing-library/react';
import { mount } from 'enzyme';
import React from 'react';
import { QueryManager } from '../../utils';
import { Capabilities } from '../../utils';
import { Capabilities, QueryManager } from '../../utils';
import { SegmentTimeline } from './segment-timeline';
@ -87,6 +86,7 @@ class MockDataQueryManager extends QueryManager<
constructor() {
super({
// eslint-disable-next-line @typescript-eslint/require-await
processQuery: async ({ timeSpan }) => {
this.queryTimeSpan = timeSpan;
},

View File

@ -22,8 +22,7 @@ import { scaleLinear, scaleTime } from 'd3-scale';
import React from 'react';
import { Api } from '../../singletons';
import { Capabilities } from '../../utils';
import { formatBytes, queryDruidSql, QueryManager, uniq } from '../../utils/index';
import { Capabilities, formatBytes, queryDruidSql, QueryManager, uniq } from '../../utils';
import { StackedBarChart } from '../../visualization/stacked-bar-chart';
import { Loader } from '../loader/loader';
@ -138,9 +137,9 @@ export class SegmentTimeline extends React.PureComponent<
total: segmentSize,
};
} else {
const countDataEntry = countData[day][datasource];
const countDataEntry: number | undefined = countData[day][datasource];
countData[day][datasource] = count + (countDataEntry === undefined ? 0 : countDataEntry);
const sizeDataEntry = sizeData[day][datasource];
const sizeDataEntry: number | undefined = sizeData[day][datasource];
sizeData[day][datasource] = segmentSize + (sizeDataEntry === undefined ? 0 : sizeDataEntry);
countData[day].total += count;
sizeData[day].total += segmentSize;
@ -219,8 +218,12 @@ export class SegmentTimeline extends React.PureComponent<
return singleDatasourceData;
}
private dataQueryManager: QueryManager<{ capabilities: Capabilities; timeSpan: number }, any>;
private chartMargin = { top: 20, right: 10, bottom: 20, left: 10 };
private readonly dataQueryManager: QueryManager<
{ capabilities: Capabilities; timeSpan: number },
any
>;
private readonly chartMargin = { top: 20, right: 10, bottom: 20, left: 10 };
constructor(props: SegmentTimelineProps) {
super(props);
@ -342,8 +345,7 @@ ORDER BY "start" DESC`;
prevProps.chartHeight !== this.props.chartHeight
) {
const scales: BarChartScales | undefined = this.calculateScales();
let dataToRender: BarUnitData[] | undefined;
dataToRender = activeDatasource
const dataToRender: BarUnitData[] | undefined = activeDatasource
? singleDatasourceData
? singleDatasourceData[activeDataType][activeDatasource]
: undefined
@ -456,7 +458,7 @@ ORDER BY "start" DESC`;
if (error) {
return (
<div>
<span className={'no-data-text'}>Error when loading data: {error.message}</span>
<span className="no-data-text">Error when loading data: {error.message}</span>
</div>
);
}
@ -464,7 +466,7 @@ ORDER BY "start" DESC`;
if (xScale === null || yScale === null) {
return (
<div>
<span className={'no-data-text'}>Error when calculating scales</span>
<span className="no-data-text">Error when calculating scales</span>
</div>
);
}
@ -472,7 +474,7 @@ ORDER BY "start" DESC`;
if (data![activeDataType].length === 0) {
return (
<div>
<span className={'no-data-text'}>No data available for the time span selected</span>
<span className="no-data-text">No data available for the time span selected</span>
</div>
);
}
@ -483,7 +485,7 @@ ORDER BY "start" DESC`;
) {
return (
<div>
<span className={'no-data-text'}>
<span className="no-data-text">
No data available for <i>{activeDatasource}</i>
</span>
</div>
@ -517,20 +519,20 @@ ORDER BY "start" DESC`;
const { datasources, activeDataType, activeDatasource, timeSpan } = this.state;
return (
<div className={'segment-timeline app-view'}>
<div className="segment-timeline app-view">
{this.renderStackedBarChart()}
<div className={'side-control'}>
<div className="side-control">
<FormGroup>
<RadioGroup
onChange={(e: any) => this.setState({ activeDataType: e.target.value })}
selectedValue={activeDataType}
>
<Radio label={'Total size'} value={'sizeData'} />
<Radio label={'Segment count'} value={'countData'} />
<Radio label="Total size" value="sizeData" />
<Radio label="Segment count" value="countData" />
</RadioGroup>
</FormGroup>
<FormGroup label={'Datasource:'}>
<FormGroup label="Datasource:">
<HTMLSelect
onChange={(e: any) =>
this.setState({
@ -540,7 +542,7 @@ ORDER BY "start" DESC`;
value={activeDatasource == null ? 'all' : activeDatasource}
fill
>
<option value={'all'}>Show all</option>
<option value="all">Show all</option>
{datasources.map(d => {
return (
<option key={d} value={d}>
@ -551,7 +553,7 @@ ORDER BY "start" DESC`;
</HTMLSelect>
</FormGroup>
<FormGroup label={'Period:'}>
<FormGroup label="Period:">
<HTMLSelect
onChange={(e: any) => this.onTimeSpanChange(e.target.value)}
value={timeSpan}

View File

@ -23,7 +23,7 @@ import { ShowHistory } from './show-history';
describe('show history', () => {
it('matches snapshot', () => {
const showJson = <ShowHistory endpoint={'test'} downloadFilename={'test'} />;
const showJson = <ShowHistory endpoint="test" downloadFilename="test" />;
const { container } = render(showJson);
expect(container.firstChild).toMatchSnapshot();
});

View File

@ -67,19 +67,13 @@ export const ShowHistory = React.memo(function ShowHistory(props: ShowHistoryPro
endpoint={endpoint}
/>
}
panelClassName={'panel'}
panelClassName="panel"
/>
));
return (
<div className="show-history">
<Tabs
animate
renderActiveTabPanelOnly
vertical
className={'tab-area'}
defaultSelectedTabId={0}
>
<Tabs animate renderActiveTabPanelOnly vertical className="tab-area" defaultSelectedTabId={0}>
{versions}
<Tabs.Expander />
</Tabs>

View File

@ -23,7 +23,7 @@ import { ShowJson } from './show-json';
describe('rule editor', () => {
it('matches snapshot', () => {
const showJson = <ShowJson endpoint={'test'} downloadFilename={'test'} />;
const showJson = <ShowJson endpoint="test" downloadFilename="test" />;
const { container } = render(showJson);
expect(container.firstChild).toMatchSnapshot();
});

View File

@ -24,7 +24,7 @@ import { ShowLog } from './show-log';
describe('show log', () => {
it('describe show log', () => {
const showLog = (
<ShowLog status={'RUNNING'} endpoint={'/druid/index/test/log'} downloadFilename={'test'} />
<ShowLog status="RUNNING" endpoint="/druid/index/test/log" downloadFilename="test" />
);
const { container } = render(showLog);
expect(container.firstChild).toMatchSnapshot();

View File

@ -50,8 +50,8 @@ export interface ShowLogState {
export class ShowLog extends React.PureComponent<ShowLogProps, ShowLogState> {
static CHECK_INTERVAL = 2500;
private showLogQueryManager: QueryManager<null, string>;
private log = React.createRef<HTMLTextAreaElement>();
private readonly showLogQueryManager: QueryManager<null, string>;
private readonly log = React.createRef<HTMLTextAreaElement>();
private interval: number | undefined;
constructor(props: ShowLogProps, context: any) {
@ -123,7 +123,7 @@ export class ShowLog extends React.PureComponent<ShowLogProps, ShowLogState> {
delete this.interval;
}
private handleCheckboxChange = () => {
private readonly handleCheckboxChange = () => {
const { tail } = this.state;
const nextTail = !tail;

View File

@ -23,7 +23,7 @@ import { ShowValue } from './show-value';
describe('rule editor', () => {
it('matches snapshot', () => {
const showJson = <ShowValue endpoint={'test'} downloadFilename={'test'} />;
const showJson = <ShowValue endpoint="test" downloadFilename="test" />;
const { container } = render(showJson);
expect(container.firstChild).toMatchSnapshot();
});

View File

@ -83,7 +83,7 @@ export const SuggestibleInput = React.memo(function SuggestibleInput(props: Sugg
rightElement={
suggestions && (
<Popover
boundary={'window'}
boundary="window"
content={
<Menu>
{suggestions.map(suggestion => {

View File

@ -36,7 +36,7 @@ jest.mock('../../hooks', () => {
describe('SupervisorStatisticsTable', () => {
function makeSupervisorStatisticsTable() {
return <SupervisorStatisticsTable supervisorId="sup-id" downloadFilename={'test'} />;
return <SupervisorStatisticsTable supervisorId="sup-id" downloadFilename="test" />;
}
it('matches snapshot on init', () => {

View File

@ -27,7 +27,7 @@ describe('TimedButton', () => {
<TimedButton
delays={[{ label: 'timeValue', delay: 1000 }]}
onRefresh={() => null}
label={'Select delay'}
label="Select delay"
defaultDelay={1000}
/>,
);

View File

@ -29,7 +29,7 @@ export const WarningChecklist = React.memo(function WarningChecklist(props: Warn
const [checked, setChecked] = useState<Record<string, boolean>>({});
function doCheck(check: string) {
const newChecked = Object.assign({}, checked);
const newChecked = { ...checked };
newChecked[check] = !newChecked[check];
setChecked(newChecked);

View File

@ -53,7 +53,7 @@ export class ConsoleApplication extends React.PureComponent<
ConsoleApplicationProps,
ConsoleApplicationState
> {
private capabilitiesQueryManager: QueryManager<null, Capabilities>;
private readonly capabilitiesQueryManager: QueryManager<null, Capabilities>;
static shownNotifications() {
AppToaster.show({
@ -119,47 +119,47 @@ export class ConsoleApplication extends React.PureComponent<
}, 50);
}
private goToLoadData = (supervisorId?: string, taskId?: string) => {
private readonly goToLoadData = (supervisorId?: string, taskId?: string) => {
if (taskId) this.taskId = taskId;
if (supervisorId) this.supervisorId = supervisorId;
window.location.hash = 'load-data';
this.resetInitialsWithDelay();
};
private goToDatasources = (datasource: string) => {
private readonly goToDatasources = (datasource: string) => {
this.datasource = datasource;
window.location.hash = 'datasources';
this.resetInitialsWithDelay();
};
private goToSegments = (datasource: string, onlyUnavailable = false) => {
private readonly goToSegments = (datasource: string, onlyUnavailable = false) => {
this.datasource = datasource;
this.onlyUnavailable = onlyUnavailable;
window.location.hash = 'segments';
this.resetInitialsWithDelay();
};
private goToIngestionWithTaskGroupId = (taskGroupId?: string, openDialog?: string) => {
private readonly goToIngestionWithTaskGroupId = (taskGroupId?: string, openDialog?: string) => {
this.taskGroupId = taskGroupId;
if (openDialog) this.openDialog = openDialog;
window.location.hash = 'ingestion';
this.resetInitialsWithDelay();
};
private goToIngestionWithDatasource = (datasource?: string, openDialog?: string) => {
private readonly goToIngestionWithDatasource = (datasource?: string, openDialog?: string) => {
this.datasource = datasource;
if (openDialog) this.openDialog = openDialog;
window.location.hash = 'ingestion';
this.resetInitialsWithDelay();
};
private goToQuery = (initQuery: string) => {
private readonly goToQuery = (initQuery: string) => {
this.initQuery = initQuery;
window.location.hash = 'query';
this.resetInitialsWithDelay();
};
private wrapInViewContainer = (
private readonly wrapInViewContainer = (
active: HeaderActiveTab,
el: JSX.Element,
classType: 'normal' | 'narrow-pad' | 'thin' = 'normal',
@ -174,12 +174,12 @@ export class ConsoleApplication extends React.PureComponent<
);
};
private wrappedHomeView = () => {
private readonly wrappedHomeView = () => {
const { capabilities } = this.state;
return this.wrapInViewContainer(null, <HomeView capabilities={capabilities} />);
};
private wrappedLoadDataView = () => {
private readonly wrappedLoadDataView = () => {
const { exampleManifestsUrl } = this.props;
return this.wrapInViewContainer(
@ -194,7 +194,7 @@ export class ConsoleApplication extends React.PureComponent<
);
};
private wrappedQueryView = () => {
private readonly wrappedQueryView = () => {
const { defaultQueryContext, mandatoryQueryContext } = this.props;
return this.wrapInViewContainer(
@ -208,7 +208,7 @@ export class ConsoleApplication extends React.PureComponent<
);
};
private wrappedDatasourcesView = () => {
private readonly wrappedDatasourcesView = () => {
const { capabilities } = this.state;
return this.wrapInViewContainer(
'datasources',
@ -222,7 +222,7 @@ export class ConsoleApplication extends React.PureComponent<
);
};
private wrappedSegmentsView = () => {
private readonly wrappedSegmentsView = () => {
const { capabilities } = this.state;
return this.wrapInViewContainer(
'segments',
@ -235,7 +235,7 @@ export class ConsoleApplication extends React.PureComponent<
);
};
private wrappedIngestionView = () => {
private readonly wrappedIngestionView = () => {
const { capabilities } = this.state;
return this.wrapInViewContainer(
'ingestion',
@ -251,7 +251,7 @@ export class ConsoleApplication extends React.PureComponent<
);
};
private wrappedServicesView = () => {
private readonly wrappedServicesView = () => {
const { capabilities } = this.state;
return this.wrapInViewContainer(
'services',
@ -263,7 +263,7 @@ export class ConsoleApplication extends React.PureComponent<
);
};
private wrappedLookupsView = () => {
private readonly wrappedLookupsView = () => {
return this.wrapInViewContainer('lookups', <LookupsView />);
};

View File

@ -29,9 +29,9 @@ describe('async action dialog', () => {
return Promise.resolve();
}}
onClose={() => {}}
confirmButtonText={'test'}
successText={'test'}
failText={'test'}
confirmButtonText="test"
successText="test"
failText="test"
/>
);
render(asyncActionDialog);

View File

@ -28,7 +28,7 @@ describe('CompactionDialog', () => {
onClose={() => {}}
onSave={() => {}}
onDelete={() => {}}
datasource={'test1'}
datasource="test1"
compactionConfig={undefined}
/>,
);
@ -41,7 +41,7 @@ describe('CompactionDialog', () => {
onClose={() => {}}
onSave={() => {}}
onDelete={() => {}}
datasource={'test1'}
datasource="test1"
compactionConfig={{
dataSource: 'test1',
tuningConfig: { partitionsSpec: { type: 'dynamic' } },
@ -57,7 +57,7 @@ describe('CompactionDialog', () => {
onClose={() => {}}
onSave={() => {}}
onDelete={() => {}}
datasource={'test1'}
datasource="test1"
compactionConfig={{
dataSource: 'test1',
tuningConfig: { partitionsSpec: { type: 'hashed' } },
@ -73,7 +73,7 @@ describe('CompactionDialog', () => {
onClose={() => {}}
onSave={() => {}}
onDelete={() => {}}
datasource={'test1'}
datasource="test1"
compactionConfig={{
dataSource: 'test1',
tuningConfig: { partitionsSpec: { type: 'single_dim' } },

View File

@ -20,7 +20,7 @@ import { Button, Classes, Dialog, Intent } from '@blueprintjs/core';
import React, { useState } from 'react';
import { AutoForm, FormJsonSelector, FormJsonTabs, JsonInput } from '../../components';
import { CompactionConfig, COMPACTION_CONFIG_FIELDS } from '../../druid-models';
import { COMPACTION_CONFIG_FIELDS, CompactionConfig } from '../../druid-models';
import './compaction-dialog.scss';

View File

@ -20,7 +20,6 @@ import { Intent } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import React, { useState } from 'react';
import { SnitchDialog } from '..';
import {
AutoForm,
ExternalLink,
@ -28,11 +27,12 @@ import {
FormJsonTabs,
JsonInput,
} from '../../components';
import { CoordinatorDynamicConfig, COORDINATOR_DYNAMIC_CONFIG_FIELDS } from '../../druid-models';
import { COORDINATOR_DYNAMIC_CONFIG_FIELDS, CoordinatorDynamicConfig } from '../../druid-models';
import { useQueryManager } from '../../hooks';
import { getLink } from '../../links';
import { Api, AppToaster } from '../../singletons';
import { getDruidErrorMessage } from '../../utils';
import { SnitchDialog } from '..';
import './coordinator-dynamic-config-dialog.scss';

View File

@ -74,7 +74,7 @@ export const EditContextDialog = React.memo(function EditContextDialog(
}
return (
<Dialog className="edit-context-dialog" isOpen onClose={onClose} title={'Edit query context'}>
<Dialog className="edit-context-dialog" isOpen onClose={onClose} title="Edit query context">
<TextArea value={queryContextString} onChange={handleTextChange} autoFocus />
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
{error && (
@ -82,10 +82,10 @@ export const EditContextDialog = React.memo(function EditContextDialog(
{error}
</Callout>
)}
<div className={'edit-context-dialog-buttons'}>
<Button text={'Close'} onClick={onClose} />
<div className="edit-context-dialog-buttons">
<Button text="Close" onClick={onClose} />
<Button
text={'Save'}
text="Save"
intent={Intent.PRIMARY}
disabled={Boolean(error)}
onClick={

View File

@ -17,10 +17,10 @@
*/
export * from './about-dialog/about-dialog';
export * from './doctor-dialog/doctor-dialog';
export * from './async-action-dialog/async-action-dialog';
export * from './compaction-dialog/compaction-dialog';
export * from './coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog';
export * from './doctor-dialog/doctor-dialog';
export * from './history-dialog/history-dialog';
export * from './lookup-edit-dialog/lookup-edit-dialog';
export * from './overlord-dynamic-config-dialog/overlord-dynamic-config-dialog';

View File

@ -28,9 +28,9 @@ describe('LookupEditDialog', () => {
onClose={() => {}}
onSubmit={() => {}}
onChange={() => {}}
lookupName={'test'}
lookupTier={'test'}
lookupVersion={'test'}
lookupName="test"
lookupTier="test"
lookupVersion="test"
lookupSpec={{ type: 'map', map: { a: 1 } }}
isEdit={false}
allLookupTiers={['__default', 'alt-tier']}

View File

@ -27,9 +27,8 @@ import {
} from '@blueprintjs/core';
import React, { useState } from 'react';
import { AutoForm, JsonInput } from '../../components';
import { FormJsonSelector, FormJsonTabs } from '../../components';
import { isLookupInvalid, LookupSpec, LOOKUP_FIELDS } from '../../druid-models';
import { AutoForm, FormJsonSelector, FormJsonTabs, JsonInput } from '../../components';
import { isLookupInvalid, LOOKUP_FIELDS, LookupSpec } from '../../druid-models';
import './lookup-edit-dialog.scss';

View File

@ -20,13 +20,13 @@ import { Intent } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import React, { useState } from 'react';
import { SnitchDialog } from '..';
import { AutoForm, ExternalLink } from '../../components';
import { OverlordDynamicConfig, OVERLORD_DYNAMIC_CONFIG_FIELDS } from '../../druid-models';
import { OVERLORD_DYNAMIC_CONFIG_FIELDS, OverlordDynamicConfig } from '../../druid-models';
import { useQueryManager } from '../../hooks';
import { getLink } from '../../links';
import { Api, AppToaster } from '../../singletons';
import { getDruidErrorMessage } from '../../utils';
import { SnitchDialog } from '..';
import './overlord-dynamic-config-dialog.scss';

View File

@ -52,8 +52,8 @@ export const QueryHistoryDialog = React.memo(function QueryHistoryDialog(
id={index}
key={index}
title={record.version}
panel={<TextArea readOnly value={record.queryString} className={'text-area'} />}
panelClassName={'panel'}
panel={<TextArea readOnly value={record.queryString} className="text-area" />}
panelClassName="panel"
/>
));
@ -62,7 +62,7 @@ export const QueryHistoryDialog = React.memo(function QueryHistoryDialog(
animate
renderActiveTabPanelOnly
vertical
className={'tab-area'}
className="tab-area"
selectedTabId={activeTab}
onChange={(t: number) => setActiveTab(t)}
>

View File

@ -26,7 +26,7 @@ describe('query plan dialog', () => {
const queryPlanDialog = (
<QueryPlanDialog
setQueryString={() => null}
explainResult={'test'}
explainResult="test"
explainError={undefined}
onClose={() => {}}
/>

View File

@ -25,7 +25,7 @@ describe('retention dialog', () => {
it('matches snapshot', () => {
const retentionDialog = (
<RetentionDialog
datasource={'test-datasource'}
datasource="test-datasource"
rules={[
{
period: 'P1000Y',

View File

@ -20,13 +20,13 @@ import { Button, Divider, FormGroup } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import React, { useState } from 'react';
import { SnitchDialog } from '..';
import { ExternalLink, RuleEditor } from '../../components';
import { useQueryManager } from '../../hooks';
import { getLink } from '../../links';
import { Api } from '../../singletons';
import { swapElements } from '../../utils';
import { Rule, RuleUtil } from '../../utils/load-rule';
import { SnitchDialog } from '..';
import './retention-dialog.scss';

View File

@ -26,9 +26,7 @@ describe('clipboard dialog', () => {
const compactionDialog = (
<ShowValueDialog
onClose={() => {}}
str={
'Bot: Automatska zamjena teksta (-[[Administrativna podjela Meksika|Admin]] +[[Administrativna podjela Meksika|Admi]])'
}
str="Bot: Automatska zamjena teksta (-[[Administrativna podjela Meksika|Admin]] +[[Administrativna podjela Meksika|Admi]])"
/>
);
render(compactionDialog);

View File

@ -45,8 +45,8 @@ export const ShowValueDialog = React.memo(function ShowValueDialog(props: ShowVa
<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={handleCopy} />
<Button text={'Close'} intent={Intent.PRIMARY} onClick={onClose} />
<Button icon={IconNames.DUPLICATE} text="Copy" onClick={handleCopy} />
<Button text="Close" intent={Intent.PRIMARY} onClick={onClose} />
</div>
</Dialog>
);

View File

@ -179,7 +179,7 @@ export class SnitchDialog extends React.PureComponent<SnitchDialogProps, SnitchD
if (showFinalStep) return this.renderFinalStep();
if (showHistory) return this.renderHistoryDialog();
const propsClone: any = Object.assign({}, this.props);
const propsClone: any = { ...this.props };
propsClone.className = classNames('snitch-dialog', propsClone.className);
return (
<Dialog isOpen {...propsClone} canOutsideClickClose={false}>

View File

@ -23,7 +23,7 @@ import { SpecDialog } from './spec-dialog';
describe('spec dialog', () => {
it('matches snapshot no initSpec', () => {
const specDialog = <SpecDialog onSubmit={() => {}} onClose={() => {}} title={'test'} />;
const specDialog = <SpecDialog onSubmit={() => {}} onClose={() => {}} title="test" />;
render(specDialog);
expect(document.body.lastChild).toMatchSnapshot();
});
@ -34,7 +34,7 @@ describe('spec dialog', () => {
initSpec={{ type: 'some-spec' }}
onSubmit={() => {}}
onClose={() => {}}
title={'test'}
title="test"
/>
);
render(specDialog);

View File

@ -27,7 +27,7 @@ describe('supervisor table action dialog', () => {
it('matches snapshot', () => {
const supervisorTableActionDialog = (
<SupervisorTableActionDialog
supervisorId={'test'}
supervisorId="test"
actions={[basicAction, basicAction, basicAction, basicAction]}
onClose={() => {}}
/>

View File

@ -26,8 +26,8 @@ describe('task table action dialog', () => {
it('matches snapshot', () => {
const taskTableActionDialog = (
<TaskTableActionDialog
status={'RUNNING'}
taskId={'test'}
status="RUNNING"
taskId="test"
actions={[basicAction]}
onClose={() => {}}
/>

View File

@ -100,9 +100,9 @@ export const COMPACTION_CONFIG_FIELDS: Field<CompactionConfig>[] = [
compaction to run, set this field.
</p>
<p>
Directly specify the number of shards to create. If this is specified and 'intervals' is
specified in the granularitySpec, the index task can skip the determine
intervals/partitions pass through the data.
Directly specify the number of shards to create. If this is specified and
&apos;intervals&apos; is specified in the granularitySpec, the index task can skip the
determine intervals/partitions pass through the data.
</p>
</>
),

View File

@ -111,12 +111,12 @@ export const COORDINATOR_DYNAMIC_CONFIG_FIELDS: Field<CoordinatorDynamicConfig>[
info: (
<>
The maximum number of segments that could be queued for loading to any given server. This
parameter could be used to speed up segments loading process, especially if there are "slow"
nodes in the cluster (with low loading speed) or if too much segments scheduled to be
replicated to some particular node (faster loading could be preferred to better segments
distribution). Desired value depends on segments loading speed, acceptable replication time
and number of nodes. Value 1000 could be a start point for a rather big cluster. Default
value is 0 (loading queue is unbounded)
parameter could be used to speed up segments loading process, especially if there are
&quot;slow&quot; nodes in the cluster (with low loading speed) or if too much segments
scheduled to be replicated to some particular node (faster loading could be preferred to
better segments distribution). Desired value depends on segments loading speed, acceptable
replication time and number of nodes. Value 1000 could be a start point for a rather big
cluster. Default value is 0 (loading queue is unbounded)
</>
),
},
@ -166,9 +166,9 @@ export const COORDINATOR_DYNAMIC_CONFIG_FIELDS: Field<CoordinatorDynamicConfig>[
emptyValue: [],
info: (
<>
List of historical services to 'decommission'. Coordinator will not assign new segments to
'decommissioning' services, and segments will be moved away from them to be placed on
non-decommissioning services at the maximum rate specified by{' '}
List of historical services to &apos;decommission&apos;. Coordinator will not assign new
segments to &apos;decommissioning&apos; services, and segments will be moved away from them
to be placed on non-decommissioning services at the maximum rate specified by{' '}
<Code>decommissioningMaxPercentOfMaxSegmentsToMove</Code>.
</>
),
@ -179,16 +179,16 @@ export const COORDINATOR_DYNAMIC_CONFIG_FIELDS: Field<CoordinatorDynamicConfig>[
defaultValue: 70,
info: (
<>
The maximum number of segments that may be moved away from 'decommissioning' services to
non-decommissioning (that is, active) services during one Coordinator run. This value is
relative to the total maximum segment movements allowed during one run which is determined
by <Code>maxSegmentsToMove</Code>. If
The maximum number of segments that may be moved away from &apos;decommissioning&apos;
services to non-decommissioning (that is, active) services during one Coordinator run. This
value is relative to the total maximum segment movements allowed during one run which is
determined by <Code>maxSegmentsToMove</Code>. If
<Code>decommissioningMaxPercentOfMaxSegmentsToMove</Code> is 0, segments will neither be
moved from or to 'decommissioning' services, effectively putting them in a sort of
"maintenance" mode that will not participate in balancing or assignment by load rules.
Decommissioning can also become stalled if there are no available active services to place
the segments. By leveraging the maximum percent of decommissioning segment movements, an
operator can prevent active services from overload by prioritizing balancing, or decrease
moved from or to &apos;decommissioning&apos; services, effectively putting them in a sort of
&quot;maintenance&quot; mode that will not participate in balancing or assignment by load
rules. Decommissioning can also become stalled if there are no available active services to
place the segments. By leveraging the maximum percent of decommissioning segment movements,
an operator can prevent active services from overload by prioritizing balancing, or decrease
decommissioning time instead. The value should be between 0 and 100.
</>
),
@ -205,8 +205,8 @@ export const COORDINATOR_DYNAMIC_CONFIG_FIELDS: Field<CoordinatorDynamicConfig>[
server, Druid iterates over the segments on the server, considering them for moving. The
default config of 100% means that every segment on every server is a candidate to be moved.
This should make sense for most small to medium-sized clusters. However, an admin may find
it preferable to drop this value lower if they don't think that it is worthwhile to consider
every single segment in the cluster each time it is looking for a segment to move.
it preferable to drop this value lower if they don&apos;t think that it is worthwhile to
consider every single segment in the cluster each time it is looking for a segment to move.
</>
),
},

View File

@ -18,16 +18,16 @@
export * from './compaction-config';
export * from './compaction-status';
export * from './coordinator-dynamic-config';
export * from './dimension-spec';
export * from './filter';
export * from './flatten-spec';
export * from './ingestion-spec';
export * from './input-format';
export * from './input-source';
export * from './lookup-spec';
export * from './metric-spec';
export * from './overlord-dynamic-config';
export * from './time';
export * from './timestamp-spec';
export * from './transform-spec';
export * from './input-source';
export * from './input-format';
export * from './flatten-spec';
export * from './filter';
export * from './dimension-spec';
export * from './metric-spec';
export * from './ingestion-spec';
export * from './coordinator-dynamic-config';
export * from './overlord-dynamic-config';

View File

@ -113,7 +113,7 @@ export function getIngestionComboType(spec: IngestionSpec): IngestionComboType |
case 'kinesis':
return ioConfig.type;
case 'index_parallel':
case 'index_parallel': {
const inputSource = deepGet(spec, 'spec.ioConfig.inputSource') || EMPTY_OBJECT;
switch (inputSource.type) {
case 'local':
@ -127,6 +127,7 @@ export function getIngestionComboType(spec: IngestionSpec): IngestionComboType |
return `${ioConfig.type}:${inputSource.type}` as IngestionComboType;
}
}
}
return;
}
@ -886,7 +887,7 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
info: (
<>
The Amazon Kinesis stream endpoint for a region. You can find a list of endpoints{' '}
<ExternalLink href={`https://docs.aws.amazon.com/general/latest/gr/ak.html`}>
<ExternalLink href="https://docs.aws.amazon.com/general/latest/gr/ak.html">
here
</ExternalLink>
.
@ -1054,8 +1055,8 @@ export function getIoConfigTuningFormFields(
<p>
The maximum number of reading tasks in a replica set. This means that the maximum
number of reading tasks will be <Code>taskCount * replicas</Code> and the total
number of tasks (reading + publishing) will be higher than this. See 'Capacity
Planning' below for more details.
number of tasks (reading + publishing) will be higher than this. See &apos;Capacity
Planning&apos; below for more details.
</p>
</>
),
@ -1212,7 +1213,7 @@ function filterIsFilename(filter: string): boolean {
}
function filenameFromPath(path: string): string | undefined {
const m = path.match(/([^\/.]+)[^\/]*?\/?$/);
const m = /([^/.]+)[^/]*?\/?$/.exec(path);
if (!m) return;
return m[1];
}
@ -1233,7 +1234,7 @@ export function guessDataSourceName(spec: IngestionSpec): string | undefined {
switch (ioConfig.type) {
case 'index':
case 'index_parallel':
case 'index_parallel': {
const inputSource = ioConfig.inputSource;
if (!inputSource) return;
@ -1249,11 +1250,12 @@ export function guessDataSourceName(spec: IngestionSpec): string | undefined {
case 's3':
case 'azure':
case 'google':
case 'google': {
const actualPath = (inputSource.objects || EMPTY_ARRAY)[0];
const uriPath =
(inputSource.uris || EMPTY_ARRAY)[0] || (inputSource.prefixes || EMPTY_ARRAY)[0];
return actualPath ? actualPath.path : uriPath ? filenameFromPath(uriPath) : undefined;
}
case 'http':
return Array.isArray(inputSource.uris)
@ -1268,6 +1270,7 @@ export function guessDataSourceName(spec: IngestionSpec): string | undefined {
}
return;
}
case 'kafka':
return ioConfig.topic;
@ -1361,9 +1364,9 @@ export const PRIMARY_PARTITION_RELATED_FORM_FIELDS: Field<IngestionSpec>[] = [
info: (
<>
The granularity to create time chunks at. Multiple segments can be created per time chunk.
For example, with 'DAY' segmentGranularity, the events of the same day fall into the same
time chunk which can be optionally further partitioned into multiple segments based on other
configurations and input size.
For example, with &apos;DAY&apos; segmentGranularity, the events of the same day fall into
the same time chunk which can be optionally further partitioned into multiple segments based
on other configurations and input size.
</>
),
},
@ -1485,8 +1488,8 @@ export function getSecondaryPartitionRelatedFormFields(
</p>
<p>
Directly specify the number of shards to create. If this is specified and
'intervals' is specified in the granularitySpec, the index task can skip the
determine intervals/partitions pass through the data.
&apos;intervals&apos; is specified in the granularitySpec, the index task can skip
the determine intervals/partitions pass through the data.
</p>
</>
),

View File

@ -166,7 +166,8 @@ export const LOOKUP_FIELDS: Field<LookupSpec>[] = [
<p>The format of the data in the lookup files.</p>
<p>
The <Code>simpleJson</Code> lookupParseSpec does not take any parameters. It is simply a
line delimited JSON file where the field is the key, and the field's value is the value.
line delimited JSON file where the field is the key, and the field&apos;s value is the
value.
</p>
</>
),

View File

@ -315,7 +315,7 @@ export function getMetricSpecSingleFieldName(metricSpec: MetricSpec): string | u
export function getMetricSpecOutputType(metricSpec: MetricSpec): string | undefined {
if (metricSpec.aggregator) return getMetricSpecOutputType(metricSpec.aggregator);
const m = String(metricSpec.type).match(/^(long|float|double)/);
const m = /^(long|float|double)/.exec(String(metricSpec.type));
if (!m) return;
return m[1];
}

View File

@ -52,11 +52,8 @@ const MIN_MICRO = MIN_MILLIS * 1000;
const MIN_NANO = MIN_MICRO * 1000;
const MAX_NANO = MIN_NANO * 1000;
// tslint:disable-next-line:max-line-length
export const AUTO_MATCHER = /^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))( ((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)$/;
// tslint:disable-next-line:max-line-length
export const ISO_MATCHER = /^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))(T((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)$/;
export const AUTO_MATCHER = /^([+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))( ((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)?(\17[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)$/;
export const ISO_MATCHER = /^([+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))(T((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)?(\17[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)$/;
// Note: AUTO and ISO are basically the same except ISO has a space as a separator instead of the T

View File

@ -24,8 +24,8 @@ import { deepGet, EMPTY_ARRAY, EMPTY_OBJECT } from '../utils';
import { IngestionSpec } from './ingestion-spec';
import {
BASIC_TIME_FORMATS,
DATETIME_TIME_FORMATS,
DATE_ONLY_TIME_FORMATS,
DATETIME_TIME_FORMATS,
OTHER_TIME_FORMATS,
} from './time';
import { Transform } from './transform-spec';

View File

@ -17,11 +17,12 @@
*/
import 'core-js/stable';
import 'regenerator-runtime/runtime';
import './bootstrap/ace';
import React from 'react';
import ReactDOM from 'react-dom';
import 'regenerator-runtime/runtime';
import './bootstrap/ace';
import { bootstrapReactTable } from './bootstrap/react-table-defaults';
import { ConsoleApplication } from './console-application';
import { Links, setLinkOverrides } from './links';

View File

@ -16,6 +16,6 @@
* limitations under the License.
*/
export * from './use-interval';
export * from './use-global-event-listener';
export * from './use-interval';
export * from './use-query-manager';

View File

@ -56,13 +56,13 @@ export function useQueryManager<Q, R>(
return () => {
queryManager.terminate();
};
}, []);
}, [initQuery, queryManager]);
if (query) {
useEffect(() => {
if (query) {
queryManager.runQuery(query);
}, [query]);
}
}, [query, queryManager]);
return [resultState, queryManager];
}

View File

@ -17,6 +17,7 @@
*/
import 'core-js/stable';
import { configure } from 'enzyme';
import enzymeAdapterReact16 from 'enzyme-adapter-react-16';

View File

@ -46,6 +46,6 @@ export class Api {
}
static encodePath(path: string): string {
return path.replace(/[?#%&'\[\]\\]/g, c => '%' + c.charCodeAt(0).toString(16).toUpperCase());
return path.replace(/[?#%&'[\]\\]/g, c => '%' + c.charCodeAt(0).toString(16).toUpperCase());
}
}

View File

@ -17,7 +17,7 @@
*/
export class UrlBaser {
static baseUrl: string = '';
static baseUrl = '';
static base(url: string): string {
if (!url.startsWith('/')) return url;

View File

@ -46,9 +46,9 @@ export class Capabilities {
static COORDINATOR: Capabilities;
static OVERLORD: Capabilities;
private queryType: QueryType;
private coordinator: boolean;
private overlord: boolean;
private readonly queryType: QueryType;
private readonly coordinator: boolean;
private readonly overlord: boolean;
static async detectQueryType(): Promise<QueryType | undefined> {
// Check SQL endpoint
@ -108,7 +108,7 @@ export class Capabilities {
static async detectCapabilities(): Promise<Capabilities | undefined> {
const capabilitiesOverride = localStorageGetJson(LocalStorageKeys.CAPABILITIES_OVERRIDE);
if (capabilitiesOverride) return new Capabilities(capabilitiesOverride as any);
if (capabilitiesOverride) return new Capabilities(capabilitiesOverride);
const queryType = await Capabilities.detectQueryType();
if (typeof queryType === 'undefined') return;

View File

@ -16,7 +16,7 @@
* limitations under the License.
*/
const UNSAFE_CHAR = /[^a-z0-9 ,._\-;:(){}\[\]<>!@#$%^&*`~?]/gi;
const UNSAFE_CHAR = /[^a-z0-9 ,._\-;:(){}[\]<>!@#$%^&*`~?]/gi;
function escape(str: string): string {
return str.replace(UNSAFE_CHAR, s => {

View File

@ -62,9 +62,10 @@ export function getDruidErrorMessage(e: any): string {
).join(' / ') || e.message
);
case 'string':
case 'string': {
const htmlResp = parseHtmlError(data);
return htmlResp ? `HTML Error: ${htmlResp}` : e.message;
}
default:
return e.message;
@ -73,8 +74,8 @@ export function getDruidErrorMessage(e: any): string {
export class DruidError extends Error {
static parsePosition(errorMessage: string): RowColumn | undefined {
const range = String(errorMessage).match(
/from line (\d+), column (\d+) to line (\d+), column (\d+)/i,
const range = /from line (\d+), column (\d+) to line (\d+), column (\d+)/i.exec(
String(errorMessage),
);
if (range) {
return {
@ -86,7 +87,7 @@ export class DruidError extends Error {
};
}
const single = String(errorMessage).match(/at line (\d+), column (\d+)/i);
const single = /at line (\d+), column (\d+)/i.exec(String(errorMessage));
if (single) {
return {
match: single[0],
@ -108,7 +109,7 @@ export class DruidError extends Error {
static getSuggestion(errorMessage: string): QuerySuggestion | undefined {
// == is used instead of =
// ex: Encountered "= =" at line 3, column 15. Was expecting one of
const matchEquals = errorMessage.match(/Encountered "= =" at line (\d+), column (\d+)./);
const matchEquals = /Encountered "= =" at line (\d+), column (\d+)./.exec(errorMessage);
if (matchEquals) {
const line = Number(matchEquals[1]);
const column = Number(matchEquals[2]);
@ -124,8 +125,8 @@ export class DruidError extends Error {
// Incorrect quoting on table
// ex: org.apache.calcite.runtime.CalciteContextException: From line 3, column 17 to line 3, column 31: Column '#ar.wikipedia' not found in any table
const matchQuotes = errorMessage.match(
/org.apache.calcite.runtime.CalciteContextException: From line (\d+), column (\d+) to line \d+, column \d+: Column '([^']+)' not found in any table/,
const matchQuotes = /org.apache.calcite.runtime.CalciteContextException: From line (\d+), column (\d+) to line \d+, column \d+: Column '([^']+)' not found in any table/.exec(
errorMessage,
);
if (matchQuotes) {
const line = Number(matchQuotes[1]);
@ -144,7 +145,7 @@ export class DruidError extends Error {
}
// , before FROM
const matchComma = errorMessage.match(/Encountered "(FROM)" at/i);
const matchComma = /Encountered "(FROM)" at/i.exec(errorMessage);
if (matchComma) {
const fromKeyword = matchComma[1];
return {
@ -212,7 +213,7 @@ export class DruidError extends Error {
}
export async function queryDruidRune(runeQuery: Record<string, any>): Promise<any> {
let runeResultResp: AxiosResponse<any>;
let runeResultResp: AxiosResponse;
try {
runeResultResp = await Api.instance.post('/druid/v2', runeQuery);
} catch (e) {
@ -222,7 +223,7 @@ export async function queryDruidRune(runeQuery: Record<string, any>): Promise<an
}
export async function queryDruidSql<T = any>(sqlQueryPayload: Record<string, any>): Promise<T[]> {
let sqlResultResp: AxiosResponse<any>;
let sqlResultResp: AxiosResponse;
try {
sqlResultResp = await Api.instance.post('/druid/v2/sql', sqlQueryPayload);
} catch (e) {

View File

@ -56,7 +56,7 @@ export function addFilterRaw(filters: Filter[], id: string, value: string): Filt
}
export function makeTextFilter(placeholder = ''): FilterRender {
return ({ filter, onChange, key }) => {
return function TextFilter({ filter, onChange, key }) {
const filterValue = filter ? filter.value : '';
return (
<InputGroup
@ -73,7 +73,7 @@ export function makeTextFilter(placeholder = ''): FilterRender {
}
export function makeBooleanFilter(): FilterRender {
return ({ filter, onChange, key }) => {
return function BooleanFilter({ filter, onChange, key }) {
const filterValue = filter ? filter.value : '';
return (
<HTMLSelect
@ -318,7 +318,7 @@ export function sortWithPrefixSuffix(
// ----------------------------
export function downloadFile(text: string, type: string, filename: string): void {
let blobType: string = '';
let blobType;
switch (type) {
case 'json':
blobType = 'application/json';

View File

@ -16,13 +16,13 @@
* limitations under the License.
*/
export * from './general';
export * from './druid-query';
export * from './druid-lookup';
export * from './query-state';
export * from './query-manager';
export * from './query-cursor';
export * from './local-storage-keys';
export * from './column-metadata';
export * from './object-change';
export * from './capabilities';
export * from './column-metadata';
export * from './druid-lookup';
export * from './druid-query';
export * from './general';
export * from './local-storage-keys';
export * from './object-change';
export * from './query-cursor';
export * from './query-manager';
export * from './query-state';

View File

@ -19,26 +19,26 @@
import * as JSONBig from 'json-bigint-native';
export const LocalStorageKeys = {
CAPABILITIES_OVERRIDE: 'capabilities-override' as 'capabilities-override',
INGESTION_SPEC: 'ingestion-spec' as 'ingestion-spec',
DATASOURCE_TABLE_COLUMN_SELECTION: 'datasource-table-column-selection' as 'datasource-table-column-selection',
SEGMENT_TABLE_COLUMN_SELECTION: 'segment-table-column-selection' as 'segment-table-column-selection',
SUPERVISOR_TABLE_COLUMN_SELECTION: 'supervisor-table-column-selection' as 'supervisor-table-column-selection',
TASK_TABLE_COLUMN_SELECTION: 'task-table-column-selection' as 'task-table-column-selection',
SERVICE_TABLE_COLUMN_SELECTION: 'service-table-column-selection' as 'service-table-column-selection',
LOOKUP_TABLE_COLUMN_SELECTION: 'lookup-table-column-selection' as 'lookup-table-column-selection',
QUERY_KEY: 'druid-console-query' as 'druid-console-query',
QUERY_CONTEXT: 'query-context' as 'query-context',
INGESTION_VIEW_PANE_SIZE: 'ingestion-view-pane-size' as 'ingestion-view-pane-size',
QUERY_VIEW_PANE_SIZE: 'query-view-pane-size' as 'query-view-pane-size',
TASKS_REFRESH_RATE: 'task-refresh-rate' as 'task-refresh-rate',
DATASOURCES_REFRESH_RATE: 'datasources-refresh-rate' as 'datasources-refresh-rate',
SEGMENTS_REFRESH_RATE: 'segments-refresh-rate' as 'segments-refresh-rate',
SERVICES_REFRESH_RATE: 'services-refresh-rate' as 'services-refresh-rate',
SUPERVISORS_REFRESH_RATE: 'supervisors-refresh-rate' as 'supervisors-refresh-rate',
LOOKUPS_REFRESH_RATE: 'lookups-refresh-rate' as 'lookups-refresh-rate',
QUERY_HISTORY: 'query-history' as 'query-history',
LIVE_QUERY_MODE: 'live-query-mode' as 'live-query-mode',
CAPABILITIES_OVERRIDE: 'capabilities-override' as const,
INGESTION_SPEC: 'ingestion-spec' as const,
DATASOURCE_TABLE_COLUMN_SELECTION: 'datasource-table-column-selection' as const,
SEGMENT_TABLE_COLUMN_SELECTION: 'segment-table-column-selection' as const,
SUPERVISOR_TABLE_COLUMN_SELECTION: 'supervisor-table-column-selection' as const,
TASK_TABLE_COLUMN_SELECTION: 'task-table-column-selection' as const,
SERVICE_TABLE_COLUMN_SELECTION: 'service-table-column-selection' as const,
LOOKUP_TABLE_COLUMN_SELECTION: 'lookup-table-column-selection' as const,
QUERY_KEY: 'druid-console-query' as const,
QUERY_CONTEXT: 'query-context' as const,
INGESTION_VIEW_PANE_SIZE: 'ingestion-view-pane-size' as const,
QUERY_VIEW_PANE_SIZE: 'query-view-pane-size' as const,
TASKS_REFRESH_RATE: 'task-refresh-rate' as const,
DATASOURCES_REFRESH_RATE: 'datasources-refresh-rate' as const,
SEGMENTS_REFRESH_RATE: 'segments-refresh-rate' as const,
SERVICES_REFRESH_RATE: 'services-refresh-rate' as const,
SUPERVISORS_REFRESH_RATE: 'supervisors-refresh-rate' as const,
LOOKUPS_REFRESH_RATE: 'lookups-refresh-rate' as const,
QUERY_HISTORY: 'query-history' as const,
LIVE_QUERY_MODE: 'live-query-mode' as const,
};
export type LocalStorageKeys = typeof LocalStorageKeys[keyof typeof LocalStorageKeys];
@ -70,5 +70,5 @@ export function localStorageGetJson(key: LocalStorageKeys): any {
export function localStorageRemove(key: LocalStorageKeys): void {
if (typeof localStorage === 'undefined') return;
return localStorage.removeItem(key);
localStorage.removeItem(key);
}

View File

@ -40,7 +40,7 @@ describe('object-change', () => {
const thing = {
hello: {
'consumer.props': 'lol',
wow: ['a', { test: 'moon' }],
'wow': ['a', { test: 'moon' }],
},
zetrix: null,
};

View File

@ -17,7 +17,7 @@
*/
export function shallowCopy(v: any): any {
return Array.isArray(v) ? v.slice() : Object.assign({}, v);
return Array.isArray(v) ? v.slice() : { ...v };
}
export function isEmpty(v: any): boolean {
@ -32,14 +32,14 @@ export function parsePath(path: string): string[] {
const parts: string[] = [];
let rest = path;
while (rest) {
const escapedMatch = rest.match(/^\{([^{}]*)\}(?:\.(.*))?$/);
const escapedMatch = /^\{([^{}]*)\}(?:\.(.*))?$/.exec(rest);
if (escapedMatch) {
parts.push(escapedMatch[1]);
rest = escapedMatch[2];
continue;
}
const normalMatch = rest.match(/^([^.]*)(?:\.(.*))?$/);
const normalMatch = /^([^.]*)(?:\.(.*))?$/.exec(rest);
if (normalMatch) {
parts.push(normalMatch[1]);
rest = normalMatch[2];
@ -70,7 +70,7 @@ export function deepGet<T extends Record<string, any>>(value: T, path: string):
export function deepSet<T extends Record<string, any>>(value: T, path: string, x: any): T {
const parts = parsePath(path);
let myKey = parts.shift() as string; // Must be defined
let myKey = parts.shift()!; // Must be defined
const valueCopy = shallowCopy(value);
if (Array.isArray(valueCopy) && isAppend(myKey)) myKey = String(valueCopy.length);
if (parts.length) {
@ -102,7 +102,7 @@ export function deepSetMulti<T extends Record<string, any>>(
export function deepDelete<T extends Record<string, any>>(value: T, path: string): T {
const valueCopy = shallowCopy(value);
const parts = parsePath(path);
const firstKey = parts.shift() as string; // Must be defined
const firstKey = parts.shift()!; // Must be defined
if (parts.length) {
const firstKeyValue = value[firstKey];
if (firstKeyValue) {

View File

@ -16,8 +16,7 @@
* limitations under the License.
*/
import axios from 'axios';
import { CancelToken } from 'axios';
import axios, { CancelToken } from 'axios';
import debounce from 'lodash.debounce';
import { QueryState } from './query-state';
@ -34,12 +33,13 @@ export interface QueryManagerOptions<Q, R> {
}
export class QueryManager<Q, R> {
private processQuery: (
private readonly processQuery: (
query: Q,
cancelToken: CancelToken,
setIntermediateQuery: (intermediateQuery: any) => void,
) => Promise<R>;
private onStateChange?: (queryResolve: QueryState<R>) => void;
private readonly onStateChange?: (queryResolve: QueryState<R>) => void;
private terminated = false;
private nextQuery: Q | undefined;
@ -49,8 +49,8 @@ export class QueryManager<Q, R> {
private state: QueryState<R> = QueryState.INIT;
private currentQueryId = 0;
private runWhenIdle: () => void;
private runWhenLoading: () => void;
private readonly runWhenIdle: () => void;
private readonly runWhenLoading: () => void;
constructor(options: QueryManagerOptions<Q, R>) {
this.processQuery = options.processQuery;

View File

@ -31,8 +31,8 @@ import {
MetricSpec,
PLACEHOLDER_TIMESTAMP_SPEC,
REINDEX_TIMESTAMP_SPEC,
TimestampSpec,
TIME_COLUMN,
TimestampSpec,
Transform,
TransformSpec,
upgradeSpec,
@ -419,7 +419,8 @@ export async function sampleForTimestamp(
}
const sampleTimeData = sampleTime.data;
return Object.assign({}, sampleColumns, {
return {
...sampleColumns,
data: sampleColumns.data.map((d, i) => {
// Merge the column sample with the time column sample
if (!d.parsed) return d;
@ -427,7 +428,7 @@ export async function sampleForTimestamp(
d.parsed.__time = timeDatumParsed ? timeDatumParsed.__time : null;
return d;
}),
});
};
}
export async function sampleForTransform(

View File

@ -69,12 +69,13 @@ describe('utils', () => {
it('spec-utils applyCache', () => {
expect(
applyCache(
Object.assign({}, ingestionSpec, {
{
...ingestionSpec,
samplerConfig: {
numRows: 500,
timeoutMs: 15000,
},
}),
},
[
{ make: 'Honda', model: 'Accord' },
{ make: 'Toyota', model: 'Prius' },

View File

@ -24,11 +24,11 @@ import React from 'react';
import ReactTable, { Filter } from 'react-table';
import {
ActionCell,
ActionIcon,
ACTION_COLUMN_ID,
ACTION_COLUMN_LABEL,
ACTION_COLUMN_WIDTH,
ActionCell,
ActionIcon,
BracedText,
MoreButton,
RefreshButton,
@ -71,7 +71,7 @@ import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array'
import './datasource-view.scss';
const tableColumns: Record<CapabilitiesMode, string[]> = {
full: [
'full': [
'Datasource name',
'Availability',
'Availability detail',
@ -301,8 +301,12 @@ ORDER BY 1`;
}
}
private datasourceQueryManager: QueryManager<DatasourceQuery, DatasourcesAndDefaultRules>;
private tiersQueryManager: QueryManager<Capabilities, string[]>;
private readonly datasourceQueryManager: QueryManager<
DatasourceQuery,
DatasourcesAndDefaultRules
>;
private readonly tiersQueryManager: QueryManager<Capabilities, string[]>;
constructor(props: DatasourcesViewProps, context: any) {
super(props, context);
@ -454,14 +458,14 @@ ORDER BY 1`;
});
}
private handleResize = () => {
private readonly handleResize = () => {
this.setState({
chartWidth: window.innerWidth * 0.85,
chartHeight: window.innerHeight * 0.4,
});
};
private refresh = (auto: any): void => {
private readonly refresh = (auto: any): void => {
this.datasourceQueryManager.rerunLastQuery(auto);
this.tiersQueryManager.rerunLastQuery(auto);
};
@ -695,7 +699,7 @@ ORDER BY 1`;
);
}
private saveRules = async (datasource: string, rules: Rule[], comment: string) => {
private readonly saveRules = async (datasource: string, rules: Rule[], comment: string) => {
try {
await Api.instance.post(`/druid/coordinator/v1/rules/${Api.encodePath(datasource)}`, rules, {
headers: {
@ -718,7 +722,7 @@ ORDER BY 1`;
this.fetchDatasourceData();
};
private editDefaultRules = () => {
private readonly editDefaultRules = () => {
this.setState({ retentionDialogOpenOn: undefined });
setTimeout(() => {
this.setState(state => {
@ -735,7 +739,7 @@ ORDER BY 1`;
}, 50);
};
private saveCompaction = async (compactionConfig: any) => {
private readonly saveCompaction = async (compactionConfig: any) => {
if (!compactionConfig) return;
try {
await Api.instance.post(`/druid/coordinator/v1/config/compaction`, compactionConfig);
@ -749,7 +753,7 @@ ORDER BY 1`;
}
};
private deleteCompaction = async () => {
private readonly deleteCompaction = () => {
const { compactionDialogOpenOn } = this.state;
if (!compactionDialogOpenOn) return;
const datasource = compactionDialogOpenOn.datasource;
@ -1417,7 +1421,7 @@ ORDER BY 1`;
/>
</ViewControlBar>
{showChart && (
<div className={'chart-container'}>
<div className="chart-container">
<SegmentTimeline
capabilities={capabilities}
chartHeight={chartHeight}

View File

@ -21,8 +21,7 @@ import React from 'react';
import { useQueryManager } from '../../../hooks';
import { Api } from '../../../singletons';
import { pluralIfNeeded, queryDruidSql } from '../../../utils';
import { Capabilities } from '../../../utils';
import { Capabilities, pluralIfNeeded, queryDruidSql } from '../../../utils';
import { HomeViewCard } from '../home-view-card/home-view-card';
export interface DatasourcesCardProps {
@ -52,9 +51,9 @@ export const DatasourcesCard = React.memo(function DatasourcesCard(props: Dataso
return (
<HomeViewCard
className="datasources-card"
href={'#datasources'}
href="#datasources"
icon={IconNames.MULTI_SELECT}
title={'Datasources'}
title="Datasources"
loading={datasourceCountState.loading}
error={datasourceCountState.error}
>

View File

@ -27,9 +27,9 @@ describe('home view card', () => {
const homeViewCard = (
<HomeViewCard
className="some-card"
href={'#somewhere'}
href="#somewhere"
icon={IconNames.DATABASE}
title={'Something'}
title="Something"
loading={false}
error={undefined}
>

Some files were not shown because too many files have changed in this diff Show More