Web console: Force intervals config (#8514)

* make sure intervals are required

* all truncated values everywhere

* continue to spec when going from tasks table

* remove unused thigns

* fix alert
This commit is contained in:
Vadim Ogievetsky 2019-09-12 00:35:04 -07:00 committed by Clint Wylie
parent 0145642d8b
commit a6eca5e935
7 changed files with 75 additions and 48 deletions

View File

@ -16,7 +16,7 @@
* limitations under the License.
*/
import { TextArea } from '@blueprintjs/core';
import { Intent, TextArea } from '@blueprintjs/core';
import React from 'react';
import { compact } from '../../utils';
@ -28,6 +28,7 @@ export interface ArrayInputProps {
placeholder?: string;
large?: boolean;
disabled?: boolean;
intent?: Intent;
}
export class ArrayInput extends React.PureComponent<ArrayInputProps, { stringValue: string }> {
@ -51,7 +52,7 @@ export class ArrayInput extends React.PureComponent<ArrayInputProps, { stringVal
};
render(): JSX.Element {
const { className, placeholder, large, disabled } = this.props;
const { className, placeholder, large, disabled, intent } = this.props;
const { stringValue } = this.state;
return (
<TextArea
@ -61,6 +62,7 @@ export class ArrayInput extends React.PureComponent<ArrayInputProps, { stringVal
placeholder={placeholder}
large={large}
disabled={disabled}
intent={intent}
fill
/>
);

View File

@ -244,15 +244,21 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
private renderStringArrayInput(field: Field<T>): JSX.Element {
const { model, large } = this.props;
const modelValue = deepGet(model as any, field.name);
return (
<ArrayInput
values={deepGet(model as any, field.name) || []}
values={modelValue || []}
onChange={(v: any) => {
this.fieldChange(field, v);
}}
placeholder={field.placeholder}
large={large}
disabled={AutoForm.evaluateFunctor(field.disabled, model)}
intent={
AutoForm.evaluateFunctor(field.required, model) && modelValue == null
? Intent.PRIMARY
: undefined
}
/>
);
}

View File

@ -19,15 +19,19 @@
import { IconNames } from '@blueprintjs/icons';
import React from 'react';
import { ShowValueDialog } from '../../dialogs/show-value-dialog/show-value-dialog';
import { ActionIcon } from '../action-icon/action-icon';
import './table-cell.scss';
export interface NullTableCellProps {
export interface TableCellProps {
value?: any;
timestamp?: boolean;
unparseable?: boolean;
openModal?: (str: string) => void;
}
export interface TableCellState {
showValue?: string;
}
interface ShortParts {
@ -36,26 +40,9 @@ interface ShortParts {
suffix: string;
}
export class TableCell extends React.PureComponent<NullTableCellProps> {
export class TableCell extends React.PureComponent<TableCellProps, TableCellState> {
static MAX_CHARS_TO_SHOW = 50;
possiblyTruncate(str: string): React.ReactNode {
if (str.length <= TableCell.MAX_CHARS_TO_SHOW) return str;
const { prefix, omitted, suffix } = TableCell.shortenString(str);
return (
<span className="table-cell truncated">
{prefix}
<span className="omitted">{omitted}</span>
{suffix}
<ActionIcon
icon={IconNames.MORE}
onClick={() => (this.props.openModal ? this.props.openModal(str) : null)}
/>
</span>
);
}
static shortenString(str: string): ShortParts {
// Print something like:
// BAAAArAAEiQKpDAEAACwZCBAGSBgiSEAAAAQpAIDwAg...23 omitted...gwiRoQBJIC
@ -69,6 +56,35 @@ export class TableCell extends React.PureComponent<NullTableCellProps> {
};
}
constructor(props: TableCellProps) {
super(props);
this.state = {};
}
private renderShowValueDialog(): JSX.Element | undefined {
const { showValue } = this.state;
if (!showValue) return;
return (
<ShowValueDialog onClose={() => this.setState({ showValue: undefined })} str={showValue} />
);
}
private renderTruncated(str: string): React.ReactNode {
if (str.length <= TableCell.MAX_CHARS_TO_SHOW) return str;
const { prefix, omitted, suffix } = TableCell.shortenString(str);
return (
<span className="table-cell truncated">
{prefix}
<span className="omitted">{omitted}</span>
{suffix}
<ActionIcon icon={IconNames.MORE} onClick={() => this.setState({ showValue: str })} />
{this.renderShowValueDialog()}
</span>
);
}
render(): React.ReactNode {
const { value, timestamp, unparseable } = this.props;
if (unparseable) {
@ -81,9 +97,9 @@ export class TableCell extends React.PureComponent<NullTableCellProps> {
</span>
);
} else if (Array.isArray(value)) {
return this.possiblyTruncate(`[${value.join(', ')}]`);
return this.renderTruncated(`[${value.join(', ')}]`);
} else {
return this.possiblyTruncate(String(value));
return this.renderTruncated(String(value));
}
} else {
if (timestamp) {

View File

@ -588,7 +588,7 @@ export interface GranularitySpec {
queryGranularity?: string;
segmentGranularity?: string;
rollup?: boolean;
intervals?: string;
intervals?: string | string[];
}
export interface MetricSpec {
@ -1541,11 +1541,11 @@ export interface TuningConfig {
fetchThreads?: number;
}
export function invalidTuningConfig(tuningConfig: TuningConfig): boolean {
export function invalidTuningConfig(tuningConfig: TuningConfig, intervals: any): boolean {
return Boolean(
tuningConfig.type === 'index_parallel' &&
tuningConfig.forceGuaranteedRollup &&
!tuningConfig.numShards,
(!tuningConfig.numShards || !intervals),
);
}

View File

@ -50,7 +50,6 @@ import {
Loader,
} from '../../components';
import { AsyncActionDialog } from '../../dialogs';
import { ShowValueDialog } from '../../dialogs/show-value-dialog/show-value-dialog';
import { AppToaster } from '../../singletons/toaster';
import { UrlBaser } from '../../singletons/url-baser';
import {
@ -251,8 +250,6 @@ export interface LoadDataViewState {
showResetConfirm: boolean;
newRollup?: boolean;
newDimensionMode?: DimensionMode;
showViewValueModal: boolean;
str: string;
// welcome
overlordModules?: string[];
@ -317,8 +314,6 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
// dialogs / modals
showResetConfirm: false,
showViewValueModal: false,
str: '',
// general
sampleStrategy: 'start',
@ -507,7 +502,6 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
{step === 'loading' && this.renderLoading()}
{this.renderResetConfirm()}
{this.renderViewValueModal()}
</div>
);
}
@ -651,15 +645,6 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
);
}
renderViewValueModal(): JSX.Element | undefined {
const { showViewValueModal, str } = this.state;
if (!showViewValueModal) return;
return (
<ShowValueDialog onClose={() => this.setState({ showViewValueModal: false })} str={str} />
);
}
renderWelcomeStepMessage(): JSX.Element | undefined {
const { selectedComboType, exampleManifests } = this.state;
@ -1225,7 +1210,6 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
)}
</div>
<ParseDataTable
openModal={str => this.setState({ showViewValueModal: true, str: str })}
sampleData={parserQueryState.data}
columnFilter={columnFilter}
canFlatten={canFlatten}
@ -2649,6 +2633,25 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
model={tuningConfig}
onChange={t => this.updateSpec(deepSet(spec, 'tuningConfig', t))}
/>
<AutoForm
fields={[
{
name: 'dataSchema.granularitySpec.intervals',
label: 'Time intervals',
type: 'string-array',
placeholder: 'ex: 2018-01-01/2018-06-01',
required: s => Boolean(deepGet(s, 'tuningConfig.forceGuaranteedRollup')),
info: (
<>
A comma separated list of intervals for the raw data being ingested. Ignored for
real-time ingestion.
</>
),
},
]}
model={spec}
onChange={s => this.updateSpec(s)}
/>
</div>
<div className="control">
<Callout className="intro">
@ -2658,7 +2661,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
{this.renderParallelPickerIfNeeded()}
</div>
{this.renderNextBar({
disabled: invalidTuningConfig(tuningConfig),
disabled: invalidTuningConfig(tuningConfig, granularitySpec.intervals),
})}
</>
);
@ -2864,6 +2867,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
try {
const resp = await axios.get(`/druid/indexer/v1/supervisor/${initSupervisorId}`);
this.updateSpec(resp.data);
this.setState({ continueToSpec: true });
this.updateStep('spec');
} catch (e) {
AppToaster.show({
@ -2879,6 +2883,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
try {
const resp = await axios.get(`/druid/indexer/v1/task/${initTaskId}`);
this.updateSpec(resp.data.payload);
this.setState({ continueToSpec: true });
this.updateStep('spec');
} catch (e) {
AppToaster.show({

View File

@ -35,7 +35,6 @@ describe('parse data table', () => {
const parseDataTable = (
<ParseDataTable
openModal={() => {}}
sampleData={sampleData}
columnFilter=""
canFlatten={false}

View File

@ -34,7 +34,6 @@ export interface ParseDataTableProps {
flattenedColumnsOnly: boolean;
flattenFields: FlattenField[];
onFlattenFieldSelect: (field: FlattenField, index: number) => void;
openModal: (str: string) => void;
}
export class ParseDataTable extends React.PureComponent<ParseDataTableProps> {
@ -78,7 +77,7 @@ export class ParseDataTable extends React.PureComponent<ParseDataTableProps> {
if (row.original.unparseable) {
return <TableCell unparseable />;
}
return <TableCell value={row.value} openModal={str => this.props.openModal(str)} />;
return <TableCell value={row.value} />;
},
headerClassName: classNames({
flattened: flattenField,