Web console: Data loader user feedback changes (#8770)

* init fixes

* cleaning styling issues

* more conversion types
This commit is contained in:
Vadim Ogievetsky 2019-10-29 08:42:51 -07:00 committed by Fangjin Yang
parent 3e9723e3ce
commit a95e3d438e
25 changed files with 518 additions and 245 deletions

View File

@ -5,7 +5,7 @@ exports[`auto-form snapshot matches snapshot 1`] = `
class="auto-form" class="auto-form"
> >
<div <div
class="bp3-form-group" class="bp3-form-group form-group-with-info"
> >
<label <label
class="bp3-label" class="bp3-label"
@ -90,7 +90,7 @@ exports[`auto-form snapshot matches snapshot 1`] = `
</div> </div>
</div> </div>
<div <div
class="bp3-form-group" class="bp3-form-group form-group-with-info"
> >
<label <label
class="bp3-label" class="bp3-label"
@ -175,7 +175,7 @@ exports[`auto-form snapshot matches snapshot 1`] = `
</div> </div>
</div> </div>
<div <div
class="bp3-form-group" class="bp3-form-group form-group-with-info"
> >
<label <label
class="bp3-label" class="bp3-label"
@ -202,7 +202,7 @@ exports[`auto-form snapshot matches snapshot 1`] = `
</div> </div>
</div> </div>
<div <div
class="bp3-form-group" class="bp3-form-group form-group-with-info"
> >
<label <label
class="bp3-label" class="bp3-label"
@ -243,7 +243,7 @@ exports[`auto-form snapshot matches snapshot 1`] = `
</div> </div>
</div> </div>
<div <div
class="bp3-form-group" class="bp3-form-group form-group-with-info"
> >
<label <label
class="bp3-label" class="bp3-label"
@ -284,7 +284,7 @@ exports[`auto-form snapshot matches snapshot 1`] = `
</div> </div>
</div> </div>
<div <div
class="bp3-form-group" class="bp3-form-group form-group-with-info"
> >
<label <label
class="bp3-label" class="bp3-label"
@ -304,7 +304,7 @@ exports[`auto-form snapshot matches snapshot 1`] = `
</div> </div>
</div> </div>
<div <div
class="bp3-form-group" class="bp3-form-group form-group-with-info"
> >
<label <label
class="bp3-label" class="bp3-label"
@ -410,7 +410,7 @@ exports[`auto-form snapshot matches snapshot 1`] = `
</div> </div>
</div> </div>
<div <div
class="bp3-form-group" class="bp3-form-group form-group-with-info"
> >
<label <label
class="bp3-label" class="bp3-label"

View File

@ -16,20 +16,12 @@
* limitations under the License. * limitations under the License.
*/ */
import { import { Button, ButtonGroup, FormGroup, Intent, NumericInput } from '@blueprintjs/core';
Button,
ButtonGroup,
FormGroup,
Icon,
Intent,
NumericInput,
Popover,
} from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import React from 'react'; import React from 'react';
import { deepDelete, deepGet, deepSet } from '../../utils/object-change'; import { deepDelete, deepGet, deepSet } from '../../utils/object-change';
import { ArrayInput } from '../array-input/array-input'; import { ArrayInput } from '../array-input/array-input';
import { FormGroupWithInfo } from '../form-group-with-info/form-group-with-info';
import { JsonInput } from '../json-input/json-input'; import { JsonInput } from '../json-input/json-input';
import { SuggestibleInput, SuggestionGroup } from '../suggestible-input/suggestible-input'; import { SuggestibleInput, SuggestionGroup } from '../suggestible-input/suggestible-input';
@ -326,22 +318,13 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
const label = field.label || AutoForm.makeLabelName(field.name); const label = field.label || AutoForm.makeLabelName(field.name);
return ( return (
<FormGroup <FormGroupWithInfo
key={field.name} key={field.name}
label={label} label={label}
labelInfo={ info={field.info ? <div className="label-info-text">{field.info}</div> : undefined}
field.info && (
<Popover
content={<div className="label-info-text">{field.info}</div>}
position="left-bottom"
>
<Icon icon={IconNames.INFO_SIGN} iconSize={14} />
</Popover>
)
}
> >
{this.renderFieldInput(field)} {this.renderFieldInput(field)}
</FormGroup> </FormGroupWithInfo>
); );
}; };

View File

@ -0,0 +1,50 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`form group with info matches snapshot 1`] = `
<div
class="bp3-form-group form-group-with-info"
>
<label
class="bp3-label"
>
Goodies
<span
class="bp3-text-muted"
>
<span
class="bp3-popover-wrapper"
>
<span
class="bp3-popover-target"
>
<span
class="bp3-icon bp3-icon-info-sign"
icon="info-sign"
>
<svg
data-icon="info-sign"
height="14"
viewBox="0 0 16 16"
width="14"
>
<desc>
info-sign
</desc>
<path
d="M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zM7 3h2v2H7V3zm3 10H6v-1h1V7H6V6h3v6h1v1z"
fill-rule="evenodd"
/>
</svg>
</span>
</span>
</span>
</span>
</label>
<div
class="bp3-form-content"
>
Some buttons and stuff
</div>
</div>
`;

View File

@ -0,0 +1,29 @@
/*
* 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.
*/
.form-group-with-info {
.bp3-form-content {
position: relative;
& > .bp3-popover-wrapper {
position: absolute;
right: 0;
top: 7px;
}
}
}

View File

@ -0,0 +1,35 @@
/*
* 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 { render } from '@testing-library/react';
import React from 'react';
import { FormGroupWithInfo } from './form-group-with-info';
describe('form group with info', () => {
it('matches snapshot', () => {
const formGroupWithInfo = (
<FormGroupWithInfo label="Goodies" info={<div>Information is gold</div>}>
Some buttons and stuff
</FormGroupWithInfo>
);
const { container } = render(formGroupWithInfo);
expect(container.firstChild).toMatchSnapshot();
});
});

View File

@ -0,0 +1,53 @@
/*
* 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 { FormGroup, Icon, Popover } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import React from 'react';
import './form-group-with-info.scss';
export interface FormGroupWithInfoProps {
label?: React.ReactNode;
info?: JSX.Element | string;
inlineInfo?: boolean;
children?: React.ReactNode;
}
export const FormGroupWithInfo = React.memo(function FormGroupWithInfo(
props: FormGroupWithInfoProps,
) {
const { label, info, inlineInfo, children } = props;
const popover = (
<Popover content={info} position="left-bottom">
<Icon icon={IconNames.INFO_SIGN} iconSize={14} />
</Popover>
);
return (
<FormGroup
className="form-group-with-info"
label={label}
labelInfo={info && !inlineInfo && popover}
>
{children}
{info && inlineInfo && popover}
</FormGroup>
);
});

View File

@ -171,7 +171,7 @@ exports[`header bar matches snapshot 1`] = `
/> />
<Blueprint3.MenuItem <Blueprint3.MenuItem
disabled={false} disabled={false}
href="https://druid.apache.org/docs/latest" href="https://druid.apache.org/docs/0.16.0-incubating"
icon="th" icon="th"
multiline={false} multiline={false}
popoverProps={Object {}} popoverProps={Object {}}

View File

@ -55,7 +55,7 @@ exports[`compaction dialog matches snapshot 1`] = `
class="auto-form" class="auto-form"
> >
<div <div
class="bp3-form-group" class="bp3-form-group form-group-with-info"
> >
<label <label
class="bp3-label" class="bp3-label"
@ -168,7 +168,7 @@ exports[`compaction dialog matches snapshot 1`] = `
</div> </div>
</div> </div>
<div <div
class="bp3-form-group" class="bp3-form-group form-group-with-info"
> >
<label <label
class="bp3-label" class="bp3-label"
@ -223,7 +223,7 @@ exports[`compaction dialog matches snapshot 1`] = `
</div> </div>
</div> </div>
<div <div
class="bp3-form-group" class="bp3-form-group form-group-with-info"
> >
<label <label
class="bp3-label" class="bp3-label"
@ -336,7 +336,7 @@ exports[`compaction dialog matches snapshot 1`] = `
</div> </div>
</div> </div>
<div <div
class="bp3-form-group" class="bp3-form-group form-group-with-info"
> >
<label <label
class="bp3-label" class="bp3-label"
@ -470,7 +470,7 @@ exports[`compaction dialog matches snapshot 1`] = `
</div> </div>
</div> </div>
<div <div
class="bp3-form-group" class="bp3-form-group form-group-with-info"
> >
<label <label
class="bp3-label" class="bp3-label"
@ -583,7 +583,7 @@ exports[`compaction dialog matches snapshot 1`] = `
</div> </div>
</div> </div>
<div <div
class="bp3-form-group" class="bp3-form-group form-group-with-info"
> >
<label <label
class="bp3-label" class="bp3-label"

View File

@ -20,6 +20,7 @@ import { Button, Classes, Dialog, Intent } from '@blueprintjs/core';
import React from 'react'; import React from 'react';
import { AutoForm, ExternalLink } from '../../components'; import { AutoForm, ExternalLink } from '../../components';
import { DRUID_DOCS_VERSION } from '../../variables';
import './compaction-dialog.scss'; import './compaction-dialog.scss';
@ -125,7 +126,9 @@ export class CompactionDialog extends React.PureComponent<
type: 'json', type: 'json',
info: ( info: (
<p> <p>
<ExternalLink href="https://druid.apache.org/docs/latest/ingestion/tasks.html#task-context"> <ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/ingestion/tasks.html#task-context`}
>
Task context Task context
</ExternalLink>{' '} </ExternalLink>{' '}
for compaction tasks. for compaction tasks.
@ -143,7 +146,9 @@ export class CompactionDialog extends React.PureComponent<
type: 'json', type: 'json',
info: ( info: (
<p> <p>
<ExternalLink href="https://druid.apache.org/docs/latest/configuration/index.html#compact-task-tuningconfig"> <ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/configuration/index.html#compact-task-tuningconfig`}
>
Tuning config Tuning config
</ExternalLink>{' '} </ExternalLink>{' '}
for compaction tasks. for compaction tasks.

View File

@ -58,7 +58,7 @@ exports[`coordinator dynamic config matches snapshot 1`] = `
Edit the coordinator dynamic configuration on the fly. For more information please refer to the Edit the coordinator dynamic configuration on the fly. For more information please refer to the
<a <a
href="https://druid.apache.org/docs/latest/configuration/index.html#dynamic-configuration" href="https://druid.apache.org/docs/0.16.0-incubating/configuration/index.html#dynamic-configuration"
rel="noopener noreferrer" rel="noopener noreferrer"
target="_blank" target="_blank"
> >

View File

@ -24,6 +24,7 @@ import React from 'react';
import { AutoForm, ExternalLink } from '../../components'; import { AutoForm, ExternalLink } from '../../components';
import { AppToaster } from '../../singletons/toaster'; import { AppToaster } from '../../singletons/toaster';
import { getDruidErrorMessage, QueryManager } from '../../utils'; import { getDruidErrorMessage, QueryManager } from '../../utils';
import { DRUID_DOCS_VERSION } from '../../variables';
import { SnitchDialog } from '../snitch-dialog/snitch-dialog'; import { SnitchDialog } from '../snitch-dialog/snitch-dialog';
import './coordinator-dynamic-config-dialog.scss'; import './coordinator-dynamic-config-dialog.scss';
@ -126,7 +127,9 @@ export class CoordinatorDynamicConfigDialog extends React.PureComponent<
<p> <p>
Edit the coordinator dynamic configuration on the fly. For more information please refer Edit the coordinator dynamic configuration on the fly. For more information please refer
to the{' '} to the{' '}
<ExternalLink href="https://druid.apache.org/docs/latest/configuration/index.html#dynamic-configuration"> <ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/configuration/index.html#dynamic-configuration`}
>
documentation documentation
</ExternalLink> </ExternalLink>
. .

View File

@ -19,7 +19,7 @@ exports[`history dialog matches snapshot 1`] = `
class="bp3-dialog history-dialog" class="bp3-dialog history-dialog"
> >
<div <div
class="history-record-container" class="bp3-dialog-body history-record-container"
> >
<span <span
class="history-dialog-title" class="history-dialog-title"
@ -125,6 +125,13 @@ exports[`history dialog matches snapshot 1`] = `
</div> </div>
</div> </div>
</div> </div>
<div
class="bp3-dialog-footer"
>
<div
class="bp3-dialog-footer-actions"
/>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -16,7 +16,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Card, Dialog, Divider } from '@blueprintjs/core'; import { Card, Classes, Dialog, Divider } from '@blueprintjs/core';
import classNames from 'classnames';
import React, { ReactNode } from 'react'; import React, { ReactNode } from 'react';
import { JsonCollapse } from '../../components'; import { JsonCollapse } from '../../components';
@ -25,54 +26,50 @@ import './history-dialog.scss';
interface HistoryDialogProps { interface HistoryDialogProps {
historyRecords: any[]; historyRecords: any[];
children?: ReactNode; buttons?: ReactNode;
} }
export const HistoryDialog = React.memo(function HistoryDialog(props: HistoryDialogProps) { export const HistoryDialog = React.memo(function HistoryDialog(props: HistoryDialogProps) {
function renderRecords() { const { buttons, historyRecords } = props;
const { children, historyRecords } = props;
let content;
if (historyRecords.length === 0) {
content = <div className="no-record">No history records available</div>;
} else {
content = (
<>
<span className="history-dialog-title">History</span>
<div className="history-record-entries">
{historyRecords.map((record, i) => {
const auditInfo = record.auditInfo;
const auditTime = record.auditTime;
const formattedTime = auditTime.replace('T', ' ').substring(0, auditTime.length - 5);
return ( let content;
<div key={i} className="history-record-entry"> if (historyRecords.length === 0) {
<Card> content = <div className="no-record">No history records available</div>;
<div className="history-record-title"> } else {
<span className="history-record-title-change">Change</span> content = (
<span>{formattedTime}</span> <>
</div> <span className="history-dialog-title">History</span>
<Divider /> <div className="history-record-entries">
<p>{auditInfo.comment === '' ? '(No comment)' : auditInfo.comment}</p> {historyRecords.map((record, i) => {
<JsonCollapse stringValue={record.payload} buttonText="Payload" /> const auditInfo = record.auditInfo;
</Card> const auditTime = record.auditTime;
</div> const formattedTime = auditTime.replace('T', ' ').substring(0, auditTime.length - 5);
);
})} return (
</div> <div key={i} className="history-record-entry">
</> <Card>
); <div className="history-record-title">
} <span className="history-record-title-change">Change</span>
return ( <span>{formattedTime}</span>
<div className="history-record-container"> </div>
{content} <Divider />
{children} <p>{auditInfo.comment === '' ? '(No comment)' : auditInfo.comment}</p>
</div> <JsonCollapse stringValue={record.payload} buttonText="Payload" />
</Card>
</div>
);
})}
</div>
</>
); );
} }
return ( return (
<Dialog className="history-dialog" isOpen {...props}> <Dialog className="history-dialog" isOpen {...props}>
{renderRecords()} <div className={classNames(Classes.DIALOG_BODY, 'history-record-container')}>{content}</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>{buttons}</div>
</div>
</Dialog> </Dialog>
); );
}); });

View File

@ -58,7 +58,7 @@ exports[`overload dynamic config matches snapshot 1`] = `
Edit the overlord dynamic configuration on the fly. For more information please refer to the Edit the overlord dynamic configuration on the fly. For more information please refer to the
<a <a
href="https://druid.apache.org/docs/latest/configuration/index.html#overlord-dynamic-configuration" href="https://druid.apache.org/docs/0.16.0-incubating/configuration/index.html#overlord-dynamic-configuration"
rel="noopener noreferrer" rel="noopener noreferrer"
target="_blank" target="_blank"
> >

View File

@ -24,6 +24,7 @@ import React from 'react';
import { AutoForm, ExternalLink } from '../../components'; import { AutoForm, ExternalLink } from '../../components';
import { AppToaster } from '../../singletons/toaster'; import { AppToaster } from '../../singletons/toaster';
import { getDruidErrorMessage, QueryManager } from '../../utils'; import { getDruidErrorMessage, QueryManager } from '../../utils';
import { DRUID_DOCS_VERSION } from '../../variables';
import { SnitchDialog } from '../snitch-dialog/snitch-dialog'; import { SnitchDialog } from '../snitch-dialog/snitch-dialog';
import './overlord-dynamic-config-dialog.scss'; import './overlord-dynamic-config-dialog.scss';
@ -129,7 +130,9 @@ export class OverlordDynamicConfigDialog extends React.PureComponent<
<p> <p>
Edit the overlord dynamic configuration on the fly. For more information please refer to Edit the overlord dynamic configuration on the fly. For more information please refer to
the{' '} the{' '}
<ExternalLink href="https://druid.apache.org/docs/latest/configuration/index.html#overlord-dynamic-configuration"> <ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/configuration/index.html#overlord-dynamic-configuration`}
>
documentation documentation
</ExternalLink> </ExternalLink>
. .

View File

@ -58,7 +58,7 @@ exports[`retention dialog matches snapshot 1`] = `
Druid uses rules to determine what data should be retained in the cluster. The rules are evaluated in order from top to bottom. For more information please refer to the Druid uses rules to determine what data should be retained in the cluster. The rules are evaluated in order from top to bottom. For more information please refer to the
<a <a
href="https://druid.apache.org/docs/latest/operations/rule-configuration.html" href="https://druid.apache.org/docs/0.16.0-incubating/operations/rule-configuration.html"
target="_blank" target="_blank"
> >
documentation documentation

View File

@ -23,6 +23,7 @@ import React from 'react';
import { RuleEditor } from '../../components'; import { RuleEditor } from '../../components';
import { QueryManager } from '../../utils'; import { QueryManager } from '../../utils';
import { DRUID_DOCS_VERSION } from '../../variables';
import { SnitchDialog } from '../snitch-dialog/snitch-dialog'; import { SnitchDialog } from '../snitch-dialog/snitch-dialog';
import './retention-dialog.scss'; import './retention-dialog.scss';
@ -183,7 +184,7 @@ export class RetentionDialog extends React.PureComponent<
Druid uses rules to determine what data should be retained in the cluster. The rules are Druid uses rules to determine what data should be retained in the cluster. The rules are
evaluated in order from top to bottom. For more information please refer to the{' '} evaluated in order from top to bottom. For more information please refer to the{' '}
<a <a
href="https://druid.apache.org/docs/latest/operations/rule-configuration.html" href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/operations/rule-configuration.html`}
target="_blank" target="_blank"
> >
documentation documentation

View File

@ -116,13 +116,15 @@ export class SnitchDialog extends React.PureComponent<SnitchDialogProps, SnitchD
if (!historyRecords) return null; if (!historyRecords) return null;
return ( return (
<HistoryDialog {...this.props} historyRecords={historyRecords}> <HistoryDialog
<div className={Classes.DIALOG_FOOTER_ACTIONS}> {...this.props}
historyRecords={historyRecords}
buttons={
<Button onClick={this.back} icon={IconNames.ARROW_LEFT}> <Button onClick={this.back} icon={IconNames.ARROW_LEFT}>
Back Back
</Button> </Button>
</div> }
</HistoryDialog> />
); );
} }

View File

@ -62,17 +62,3 @@ svg {
margin-bottom: 0; margin-bottom: 0;
} }
} }
.bp3-form-group {
.bp3-form-content {
position: relative;
& > .bp3-popover-wrapper {
position: absolute;
right: 0;
top: 7px;
}
}
}
// Some SplitterLayout globals

View File

@ -21,6 +21,7 @@ import React from 'react';
import { Field } from '../components/auto-form/auto-form'; import { Field } from '../components/auto-form/auto-form';
import { ExternalLink } from '../components/external-link/external-link'; import { ExternalLink } from '../components/external-link/external-link';
import { DRUID_DOCS_VERSION } from '../variables';
import { import {
BASIC_TIME_FORMATS, BASIC_TIME_FORMATS,
@ -162,13 +163,13 @@ export function getIngestionDocLink(spec: IngestionSpec): string {
switch (type) { switch (type) {
case 'kafka': case 'kafka':
return 'https://druid.apache.org/docs/latest/development/extensions-core/kafka-ingestion.html'; return `https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/development/extensions-core/kafka-ingestion.html`;
case 'kinesis': case 'kinesis':
return 'https://druid.apache.org/docs/latest/development/extensions-core/kinesis-ingestion.html'; return `https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/development/extensions-core/kinesis-ingestion.html`;
default: default:
return 'https://druid.apache.org/docs/latest/ingestion/native-batch.html#firehoses'; return `https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/ingestion/native-batch.html#firehoses`;
} }
} }
@ -306,7 +307,9 @@ const PARSE_SPEC_FORM_FIELDS: Field<ParseSpec>[] = [
<p>The parser used to parse the data.</p> <p>The parser used to parse the data.</p>
<p> <p>
For more information see{' '} For more information see{' '}
<ExternalLink href="https://druid.apache.org/docs/latest/ingestion/data-formats.html"> <ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/ingestion/data-formats.html`}
>
the documentation the documentation
</ExternalLink> </ExternalLink>
. .
@ -573,7 +576,9 @@ const FLATTEN_FIELD_FORM_FIELDS: Field<FlattenField>[] = [
info: ( info: (
<> <>
Specify a flatten{' '} Specify a flatten{' '}
<ExternalLink href="https://druid.apache.org/docs/latest/ingestion/flatten-json"> <ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/ingestion/flatten-json`}
>
expression expression
</ExternalLink> </ExternalLink>
. .
@ -618,7 +623,9 @@ const TRANSFORM_FORM_FIELDS: Field<Transform>[] = [
info: ( info: (
<> <>
A valid Druid{' '} A valid Druid{' '}
<ExternalLink href="https://druid.apache.org/docs/latest/misc/math-expr.html"> <ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/misc/math-expr.html`}
>
expression expression
</ExternalLink> </ExternalLink>
. .
@ -820,7 +827,9 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
info: ( info: (
<p> <p>
Druid connects to raw data through{' '} Druid connects to raw data through{' '}
<ExternalLink href="https://druid.apache.org/docs/latest/ingestion/firehose.html"> <ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/ingestion/firehose.html`}
>
firehoses firehoses
</ExternalLink> </ExternalLink>
. You can change your selected firehose here. . You can change your selected firehose here.
@ -873,7 +882,9 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
required: true, required: true,
info: ( info: (
<> <>
<ExternalLink href="https://druid.apache.org/docs/latest/ingestion/firehose.html#localfirehose"> <ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/ingestion/firehose.html#localfirehose`}
>
firehose.baseDir firehose.baseDir
</ExternalLink> </ExternalLink>
<p>Specifies the directory to search recursively for files to be ingested.</p> <p>Specifies the directory to search recursively for files to be ingested.</p>
@ -888,7 +899,9 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
suggestions: ['*', '*.json', '*.json.gz', '*.csv', '*.tsv'], suggestions: ['*', '*.json', '*.json.gz', '*.csv', '*.tsv'],
info: ( info: (
<> <>
<ExternalLink href="https://druid.apache.org/docs/latest/ingestion/firehose.html#localfirehose"> <ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/ingestion/firehose.html#localfirehose`}
>
firehose.filter firehose.filter
</ExternalLink> </ExternalLink>
<p> <p>
@ -963,7 +976,9 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
info: ( info: (
<p> <p>
The{' '} The{' '}
<ExternalLink href="https://druid.apache.org/docs/latest/querying/filters.html"> <ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/querying/filters.html`}
>
filter filter
</ExternalLink>{' '} </ExternalLink>{' '}
to apply to the data as part of querying. to apply to the data as part of querying.
@ -1026,7 +1041,9 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
<> <>
<p> <p>
JSON array of{' '} JSON array of{' '}
<ExternalLink href="https://druid.apache.org/docs/latest/development/extensions-contrib/google.html"> <ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/development/extensions-contrib/google.html`}
>
Google Blobs Google Blobs
</ExternalLink> </ExternalLink>
. .
@ -1057,7 +1074,9 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
required: true, required: true,
info: ( info: (
<> <>
<ExternalLink href="https://druid.apache.org/docs/latest/development/extensions-core/kafka-ingestion#kafkasupervisorioconfig"> <ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/development/extensions-core/kafka-ingestion#kafkasupervisorioconfig`}
>
consumerProperties consumerProperties
</ExternalLink> </ExternalLink>
<p> <p>
@ -1079,7 +1098,9 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
defaultValue: {}, defaultValue: {},
info: ( info: (
<> <>
<ExternalLink href="https://druid.apache.org/docs/latest/development/extensions-core/kafka-ingestion#kafkasupervisorioconfig"> <ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/development/extensions-core/kafka-ingestion#kafkasupervisorioconfig`}
>
consumerProperties consumerProperties
</ExternalLink> </ExternalLink>
<p>A map of properties to be passed to the Kafka consumer.</p> <p>A map of properties to be passed to the Kafka consumer.</p>
@ -1129,7 +1150,9 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
info: ( info: (
<> <>
The Amazon Kinesis stream endpoint for a region. You can find a list of endpoints{' '} The Amazon Kinesis stream endpoint for a region. You can find a list of endpoints{' '}
<ExternalLink href="http://docs.aws.amazon.com/general/latest/gr/rande.html#ak_region"> <ExternalLink
href={`http://docs.aws.amazon.com/general/${DRUID_DOCS_VERSION}/gr/rande.html#ak_region`}
>
here here
</ExternalLink> </ExternalLink>
. .

View File

@ -19,13 +19,16 @@
export const LEGACY_COORDINATOR_CONSOLE = '/index.html'; export const LEGACY_COORDINATOR_CONSOLE = '/index.html';
export const LEGACY_OVERLORD_CONSOLE = '/console.html'; export const LEGACY_OVERLORD_CONSOLE = '/console.html';
// This is set to the latest available version and should be updated to the next version before release
export const DRUID_DOCS_VERSION = '0.16.0-incubating';
export const DRUID_WEBSITE = 'https://druid.apache.org'; export const DRUID_WEBSITE = 'https://druid.apache.org';
export const DRUID_GITHUB = 'https://github.com/apache/druid'; export const DRUID_GITHUB = 'https://github.com/apache/druid';
export const DRUID_DOCS = 'https://druid.apache.org/docs/latest'; export const DRUID_DOCS = `https://druid.apache.org/docs/${DRUID_DOCS_VERSION}`;
export const DRUID_DOCS_SQL = 'https://druid.apache.org/docs/latest/querying/sql.html'; export const DRUID_DOCS_SQL = `https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/querying/sql.html`;
export const DRUID_DOCS_RUNE = 'https://druid.apache.org/docs/latest/querying/querying.html'; export const DRUID_DOCS_RUNE = `https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/querying/querying.html`;
export const DRUID_COMMUNITY = 'https://druid.apache.org/community/'; export const DRUID_COMMUNITY = 'https://druid.apache.org/community/';
export const DRUID_USER_GROUP = 'https://groups.google.com/forum/#!forum/druid-user'; export const DRUID_USER_GROUP = 'https://groups.google.com/forum/#!forum/druid-user';
export const DRUID_ASF_SLACK = 'https://druid.apache.org/community/join-slack'; export const DRUID_ASF_SLACK = 'https://druid.apache.org/community/join-slack';
export const DRUID_DEVELOPER_GROUP = 'https://lists.apache.org/list.html?dev@druid.apache.org'; export const DRUID_DEVELOPER_GROUP = 'https://lists.apache.org/list.html?dev@druid.apache.org';
export const DRUID_DOCS_API = 'https://druid.apache.org/docs/latest/operations/api-reference.html'; export const DRUID_DOCS_API = `https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/operations/api-reference.html`;

View File

@ -5,7 +5,7 @@ exports[`learn more matches snapshot 1`] = `
class="learn-more" class="learn-more"
> >
<a <a
href="https://druid.apache.org/docs/latest/development/extensions-core/kinesis-ingestion.html" href="https://druid.apache.org/docs"
rel="noopener noreferrer" rel="noopener noreferrer"
target="_blank" target="_blank"
> >

View File

@ -23,9 +23,7 @@ import { LearnMore } from './learn-more';
describe('learn more', () => { describe('learn more', () => {
it('matches snapshot', () => { it('matches snapshot', () => {
const learnMore = ( const learnMore = <LearnMore href={`https://druid.apache.org/docs`} />;
<LearnMore href="https://druid.apache.org/docs/latest/development/extensions-core/kinesis-ingestion.html" />
);
const { container } = render(learnMore); const { container } = render(learnMore);
expect(container.firstChild).toMatchSnapshot(); expect(container.firstChild).toMatchSnapshot();

View File

@ -88,7 +88,7 @@
bottom: 0; bottom: 0;
left: 0; left: 0;
content: ''; content: '';
border: 2px solid #48aff0; border: 2px solid #008adc;
border-radius: 2px; border-radius: 2px;
} }
@ -195,7 +195,7 @@
left: 0; left: 0;
right: 0; right: 0;
content: ''; content: '';
border: 2px solid #ff5d10; border: 2px solid #008adc;
border-radius: 2px; border-radius: 2px;
} }
@ -208,7 +208,7 @@
} }
&.used { &.used {
background: rgba(24, 201, 201, 0.5); background: rgba(99, 129, 137, 0.5);
} }
} }
@ -218,7 +218,7 @@
} }
&.used { &.used {
background: rgba(24, 201, 201, 0.15); background: rgba(99, 129, 137, 0.15);
} }
} }
} }
@ -243,12 +243,6 @@
} }
} }
.apply-button-bar {
.revert {
margin-left: 15px;
}
}
.next-bar { .next-bar {
grid-area: next; grid-area: next;
text-align: right; text-align: right;
@ -268,19 +262,19 @@
padding: 10px; padding: 10px;
border-radius: 2px; border-radius: 2px;
margin-bottom: 15px; margin-bottom: 15px;
}
.control-buttons { .control-buttons {
position: relative; position: relative;
.bp3-button { .bp3-button {
margin-right: 15px; margin-right: 15px;
} }
.cancel { .right {
position: absolute; position: absolute;
right: 0; right: 0;
margin-right: 0; margin-right: 0;
}
} }
} }

View File

@ -31,6 +31,8 @@ import {
Icon, Icon,
IconName, IconName,
Intent, Intent,
Menu,
MenuItem,
Popover, Popover,
Switch, Switch,
TextArea, TextArea,
@ -49,6 +51,7 @@ import {
JsonInput, JsonInput,
Loader, Loader,
} from '../../components'; } from '../../components';
import { FormGroupWithInfo } from '../../components/form-group-with-info/form-group-with-info';
import { AsyncActionDialog } from '../../dialogs'; import { AsyncActionDialog } from '../../dialogs';
import { AppToaster } from '../../singletons/toaster'; import { AppToaster } from '../../singletons/toaster';
import { UrlBaser } from '../../singletons/url-baser'; import { UrlBaser } from '../../singletons/url-baser';
@ -140,6 +143,7 @@ import {
SampleStrategy, SampleStrategy,
} from '../../utils/sampler'; } from '../../utils/sampler';
import { computeFlattenPathsForData } from '../../utils/spec-utils'; import { computeFlattenPathsForData } from '../../utils/spec-utils';
import { DRUID_DOCS_VERSION } from '../../variables';
import { ExamplePicker } from './example-picker/example-picker'; import { ExamplePicker } from './example-picker/example-picker';
import { FilterTable, filterTableSelectedColumnName } from './filter-table/filter-table'; import { FilterTable, filterTableSelectedColumnName } from './filter-table/filter-table';
@ -555,7 +559,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
const previewSpecSame = this.isPreviewSpecSame(); const previewSpecSame = this.isPreviewSpecSame();
return ( return (
<FormGroup className="apply-button-bar"> <FormGroup className="control-buttons">
<Button <Button
text="Apply" text="Apply"
disabled={previewSpecSame} disabled={previewSpecSame}
@ -564,8 +568,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
/> />
{!previewSpecSame && ( {!previewSpecSame && (
<Button <Button
className="revert" text="Cancel"
icon={IconNames.UNDO}
disabled={this.isPreviewSpecSame()} disabled={this.isPreviewSpecSame()}
onClick={this.revertPreviewSpec} onClick={this.revertPreviewSpec}
/> />
@ -791,7 +794,9 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
return ( return (
<p> <p>
If you do not see your source of raw data here, you can try to ingest it by submitting a{' '} If you do not see your source of raw data here, you can try to ingest it by submitting a{' '}
<ExternalLink href="https://druid.apache.org/docs/latest/ingestion/index.html"> <ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/ingestion/index.html`}
>
JSON task or supervisor spec JSON task or supervisor spec
</ExternalLink> </ExternalLink>
. .
@ -893,7 +898,9 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
</p> </p>
<p> <p>
For more information please refer to the{' '} For more information please refer to the{' '}
<ExternalLink href="https://druid.apache.org/docs/latest/operations/including-extensions"> <ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/operations/including-extensions`}
>
documentation on loading extensions documentation on loading extensions
</ExternalLink> </ExternalLink>
. .
@ -1029,7 +1036,9 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
<Callout className="intro"> <Callout className="intro">
<p> <p>
Druid ingests raw data and converts it into a custom,{' '} Druid ingests raw data and converts it into a custom,{' '}
<ExternalLink href="https://druid.apache.org/docs/latest/design/segments.html"> <ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/design/segments.html`}
>
indexed format indexed format
</ExternalLink>{' '} </ExternalLink>{' '}
that is optimized for analytic queries. that is optimized for analytic queries.
@ -1264,7 +1273,9 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
{canFlatten && ( {canFlatten && (
<p> <p>
If you have nested data, you can{' '} If you have nested data, you can{' '}
<ExternalLink href="https://druid.apache.org/docs/latest/ingestion/index.html#flattenspec"> <ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/ingestion/index.html#flattenspec`}
>
flatten flatten
</ExternalLink>{' '} </ExternalLink>{' '}
it here. If the provided flattening capabilities are not sufficient, please it here. If the provided flattening capabilities are not sufficient, please
@ -1272,7 +1283,9 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
</p> </p>
)} )}
<p>Ensure that your data appears correctly in a row/column orientation.</p> <p>Ensure that your data appears correctly in a row/column orientation.</p>
<LearnMore href="https://druid.apache.org/docs/latest/ingestion/data-formats.html" /> <LearnMore
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/ingestion/data-formats.html`}
/>
</Callout> </Callout>
{!selectedFlattenField && ( {!selectedFlattenField && (
<> <>
@ -1362,8 +1375,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
/> />
<div className="control-buttons"> <div className="control-buttons">
<Button <Button
className="add-update" text="Apply"
text={selectedFlattenFieldIndex === -1 ? 'Add' : 'Update'}
intent={Intent.PRIMARY} intent={Intent.PRIMARY}
onClick={() => { onClick={() => {
this.updateSpec( this.updateSpec(
@ -1376,8 +1388,10 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
close(); close();
}} }}
/> />
<Button text="Cancel" onClick={close} />
{selectedFlattenFieldIndex !== -1 && ( {selectedFlattenFieldIndex !== -1 && (
<Button <Button
className="right"
icon={IconNames.TRASH} icon={IconNames.TRASH}
intent={Intent.DANGER} intent={Intent.DANGER}
onClick={() => { onClick={() => {
@ -1391,7 +1405,6 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
}} }}
/> />
)} )}
<Button className="cancel" text="Cancel" onClick={close} />
</div> </div>
</div> </div>
); );
@ -1409,7 +1422,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
/> />
<AnchorButton <AnchorButton
icon={IconNames.INFO_SIGN} icon={IconNames.INFO_SIGN}
href="https://druid.apache.org/docs/latest/ingestion/flatten-json.html" href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/ingestion/flatten-json.html`}
target="_blank" target="_blank"
minimal minimal
/> />
@ -1529,7 +1542,9 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
column. If you do not have any time columns, you can choose "Constant value" to create column. If you do not have any time columns, you can choose "Constant value" to create
a default one. a default one.
</p> </p>
<LearnMore href="https://druid.apache.org/docs/latest/ingestion/index.html#timestampspec" /> <LearnMore
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/ingestion/index.html#timestampspec`}
/>
</Callout> </Callout>
<FormGroup label="Timestamp spec"> <FormGroup label="Timestamp spec">
<ButtonGroup> <ButtonGroup>
@ -1692,12 +1707,16 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
<p className="optional">Optional</p> <p className="optional">Optional</p>
<p> <p>
Druid can perform per-row{' '} Druid can perform per-row{' '}
<ExternalLink href="https://druid.apache.org/docs/latest/ingestion/transform-spec.html#transforms"> <ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/ingestion/transform-spec.html#transforms`}
>
transforms transforms
</ExternalLink>{' '} </ExternalLink>{' '}
of column values allowing you to create new derived columns or alter existing column. of column values allowing you to create new derived columns or alter existing column.
</p> </p>
<LearnMore href="https://druid.apache.org/docs/latest/ingestion/index.html#transforms" /> <LearnMore
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/ingestion/index.html#transforms`}
/>
</Callout> </Callout>
{Boolean(transformQueryState.error && transforms.length) && ( {Boolean(transformQueryState.error && transforms.length) && (
<FormGroup> <FormGroup>
@ -1758,8 +1777,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
/> />
<div className="control-buttons"> <div className="control-buttons">
<Button <Button
className="add-update" text="Apply"
text={selectedTransformIndex === -1 ? 'Add' : 'Update'}
intent={Intent.PRIMARY} intent={Intent.PRIMARY}
onClick={() => { onClick={() => {
this.updateSpec( this.updateSpec(
@ -1772,8 +1790,10 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
close(); close();
}} }}
/> />
<Button text="Cancel" onClick={close} />
{selectedTransformIndex !== -1 && ( {selectedTransformIndex !== -1 && (
<Button <Button
className="right"
icon={IconNames.TRASH} icon={IconNames.TRASH}
intent={Intent.DANGER} intent={Intent.DANGER}
onClick={() => { onClick={() => {
@ -1787,7 +1807,6 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
}} }}
/> />
)} )}
<Button className="cancel" text="Cancel" onClick={close} />
</div> </div>
</div> </div>
); );
@ -1935,12 +1954,16 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
<p className="optional">Optional</p> <p className="optional">Optional</p>
<p> <p>
Druid can{' '} Druid can{' '}
<ExternalLink href="https://druid.apache.org/docs/latest/querying/filters.html"> <ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/querying/filters.html`}
>
filter filter
</ExternalLink>{' '} </ExternalLink>{' '}
out unwanted data by applying per-row filters. out unwanted data by applying per-row filters.
</p> </p>
<LearnMore href="https://druid.apache.org/docs/latest/ingestion/index.html#filter" /> <LearnMore
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/ingestion/index.html#filter`}
/>
</Callout> </Callout>
{!showGlobalFilter && this.renderColumnFilterControls()} {!showGlobalFilter && this.renderColumnFilterControls()}
{!selectedFilter && this.renderGlobalFilterControls()} {!selectedFilter && this.renderGlobalFilterControls()}
@ -1982,8 +2005,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
/> />
<div className="control-buttons"> <div className="control-buttons">
<Button <Button
className="add-update" text="Apply"
text={selectedFilterIndex === -1 ? 'Add' : 'Update'}
intent={Intent.PRIMARY} intent={Intent.PRIMARY}
onClick={() => { onClick={() => {
const curFilter = splitFilter(deepGet(spec, 'dataSchema.transformSpec.filter')); const curFilter = splitFilter(deepGet(spec, 'dataSchema.transformSpec.filter'));
@ -1994,8 +2016,10 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
close(); close();
}} }}
/> />
<Button text="Cancel" onClick={close} />
{selectedFilterIndex !== -1 && ( {selectedFilterIndex !== -1 && (
<Button <Button
className="right"
icon={IconNames.TRASH} icon={IconNames.TRASH}
intent={Intent.DANGER} intent={Intent.DANGER}
onClick={() => { onClick={() => {
@ -2008,7 +2032,6 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
}} }}
/> />
)} )}
<Button className="cancel" text="Cancel" onClick={close} />
</div> </div>
</div> </div>
); );
@ -2068,17 +2091,8 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
/> />
</FormGroup> </FormGroup>
<div className="control-buttons"> <div className="control-buttons">
<Button <Button text="Apply" intent={Intent.PRIMARY} onClick={() => this.queryForFilter()} />
className="add-update" <Button text="Cancel" onClick={() => this.setState({ showGlobalFilter: false })} />
text="Apply"
intent={Intent.PRIMARY}
onClick={() => this.queryForFilter()}
/>
<Button
className="cancel"
text="Close"
onClick={() => this.setState({ showGlobalFilter: false })}
/>
</div> </div>
</div> </div>
); );
@ -2205,11 +2219,36 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
want to change the type, click on the column header. want to change the type, click on the column header.
</p> </p>
)} )}
<LearnMore href="https://druid.apache.org/docs/latest/ingestion/schema-design.html" /> <LearnMore
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/ingestion/schema-design.html`}
/>
</Callout> </Callout>
{!somethingSelected && ( {!somethingSelected && (
<> <>
<FormGroup> <FormGroupWithInfo
inlineInfo
info={
<div className="label-info-text">
<p>
Select whether or not you want to set an explicit list of{' '}
<ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/ingestion/ingestion-spec.html#dimensionsspec`}
>
dimensions
</ExternalLink>{' '}
and{' '}
<ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/querying/aggregations.html`}
>
metrics
</ExternalLink>
. Explicitly setting dimensions and metrics can lead to better compression and
performance. If you disable this option, Druid will try to auto-detect fields
in your data and treat them as individual columns.
</p>
</div>
}
>
<Switch <Switch
checked={dimensionMode === 'specific'} checked={dimensionMode === 'specific'}
onChange={() => onChange={() =>
@ -2219,29 +2258,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
} }
label="Explicitly specify dimension list" label="Explicitly specify dimension list"
/> />
<Popover </FormGroupWithInfo>
content={
<div className="label-info-text">
<p>
Select whether or not you want to set an explicit list of{' '}
<ExternalLink href="https://druid.apache.org/docs/latest/ingestion/ingestion-spec.html#dimensionsspec">
dimensions
</ExternalLink>{' '}
and{' '}
<ExternalLink href="https://druid.apache.org/docs/latest/querying/aggregations.html">
metrics
</ExternalLink>
. Explicitly setting dimensions and metrics can lead to better compression
and performance. If you disable this option, Druid will try to auto-detect
fields in your data and treat them as individual columns.
</p>
</div>
}
position="left-bottom"
>
<Icon icon={IconNames.INFO_SIGN} iconSize={14} />
</Popover>
</FormGroup>
{dimensionMode === 'auto-detect' && ( {dimensionMode === 'auto-detect' && (
<AutoForm <AutoForm
fields={[ fields={[
@ -2261,43 +2278,45 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
onChange={s => this.updateSpec(s)} onChange={s => this.updateSpec(s)}
/> />
)} )}
<FormGroup> <FormGroupWithInfo
inlineInfo
info={
<div className="label-info-text">
<p>
If you enable{' '}
<ExternalLink
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/tutorials/tutorial-rollup.html`}
>
roll-up
</ExternalLink>
, Druid will try to pre-aggregate data before indexing it to conserve storage.
The primary timestamp will be truncated to the specified query granularity,
and rows containing the same string field values will be aggregated together.
</p>
<p>
If you enable rollup, you must specify which columns are{' '}
<a
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/ingestion/ingestion-spec.html#dimensionsspec`}
>
dimensions
</a>{' '}
(fields you want to group and filter on), and which are{' '}
<a
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/querying/aggregations.html`}
>
metrics
</a>{' '}
(fields you want to aggregate on).
</p>
</div>
}
>
<Switch <Switch
checked={rollup} checked={rollup}
onChange={() => this.setState({ newRollup: !rollup })} onChange={() => this.setState({ newRollup: !rollup })}
labelElement="Rollup" labelElement="Rollup"
/> />
<Popover </FormGroupWithInfo>
content={
<div className="label-info-text">
<p>
If you enable{' '}
<ExternalLink href="https://druid.apache.org/docs/latest/tutorials/tutorial-rollup.html">
roll-up
</ExternalLink>
, Druid will try to pre-aggregate data before indexing it to conserve
storage. The primary timestamp will be truncated to the specified query
granularity, and rows containing the same string field values will be
aggregated together.
</p>
<p>
If you enable rollup, you must specify which columns are{' '}
<a href="https://druid.apache.org/docs/latest/ingestion/ingestion-spec.html#dimensionsspec">
dimensions
</a>{' '}
(fields you want to group and filter on), and which are{' '}
<a href="https://druid.apache.org/docs/latest/querying/aggregations.html">
metrics
</a>{' '}
(fields you want to aggregate on).
</p>
</div>
}
position="left-bottom"
>
<Icon icon={IconNames.INFO_SIGN} iconSize={14} />
</Popover>
</FormGroup>
<AutoForm <AutoForm
fields={[ fields={[
{ {
@ -2425,6 +2444,33 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
const curDimensions = const curDimensions =
deepGet(spec, `dataSchema.parser.parseSpec.dimensionsSpec.dimensions`) || EMPTY_ARRAY; deepGet(spec, `dataSchema.parser.parseSpec.dimensionsSpec.dimensions`) || EMPTY_ARRAY;
const convertToMetricMenu = (
<Menu>
<MenuItem
text="Convert to hyperUnique metric"
onClick={() => {
const specWithoutDimension = deepDelete(
spec,
`dataSchema.parser.parseSpec.dimensionsSpec.dimensions.${selectedDimensionSpecIndex}`,
);
const specWithMetric = deepSet(
specWithoutDimension,
`dataSchema.metricsSpec.[append]`,
{
name: `unique_${selectedDimensionSpec.name}`,
type: 'hyperUnique',
fieldName: selectedDimensionSpec.name,
},
);
this.updateSpec(specWithMetric);
close();
}}
/>
</Menu>
);
return ( return (
<div className="edit-controls"> <div className="edit-controls">
<AutoForm <AutoForm
@ -2432,10 +2478,20 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
model={selectedDimensionSpec} model={selectedDimensionSpec}
onChange={selectedDimensionSpec => this.setState({ selectedDimensionSpec })} onChange={selectedDimensionSpec => this.setState({ selectedDimensionSpec })}
/> />
{selectedDimensionSpecIndex !== -1 && deepGet(spec, 'dataSchema.metricsSpec') && (
<FormGroup>
<Popover content={convertToMetricMenu}>
<Button
icon={IconNames.EXCHANGE}
text="Convert to metric..."
disabled={curDimensions.length <= 1}
/>
</Popover>
</FormGroup>
)}
<div className="control-buttons"> <div className="control-buttons">
<Button <Button
className="add-update" text="Apply"
text={selectedDimensionSpecIndex === -1 ? 'Add' : 'Update'}
intent={Intent.PRIMARY} intent={Intent.PRIMARY}
onClick={() => { onClick={() => {
this.updateSpec( this.updateSpec(
@ -2448,8 +2504,10 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
close(); close();
}} }}
/> />
<Button text="Cancel" onClick={close} />
{selectedDimensionSpecIndex !== -1 && ( {selectedDimensionSpecIndex !== -1 && (
<Button <Button
className="right"
icon={IconNames.TRASH} icon={IconNames.TRASH}
intent={Intent.DANGER} intent={Intent.DANGER}
disabled={curDimensions.length <= 1} disabled={curDimensions.length <= 1}
@ -2466,7 +2524,6 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
}} }}
/> />
)} )}
<Button className="cancel" text="Cancel" onClick={close} />
</div> </div>
</div> </div>
); );
@ -2502,6 +2559,39 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
}; };
if (selectedMetricSpec) { if (selectedMetricSpec) {
const convertToDimension = (type: string) => {
const specWithoutMetric = deepDelete(
spec,
`dataSchema.metricsSpec.${selectedMetricSpecIndex}`,
);
const specWithDimension = deepSet(
specWithoutMetric,
`dataSchema.parser.parseSpec.dimensionsSpec.dimensions.[append]`,
{
type,
name: selectedMetricSpec.fieldName,
},
);
this.updateSpec(specWithDimension);
close();
};
const convertToDimensionMenu = (
<Menu>
<MenuItem
text="Convert to STRING dimension"
onClick={() => convertToDimension('STRING')}
/>
<MenuItem text="Convert to LONG dimension" onClick={() => convertToDimension('LONG')} />
<MenuItem
text="Convert to DOUBLE dimension"
onClick={() => convertToDimension('DOUBLE')}
/>
</Menu>
);
return ( return (
<div className="edit-controls"> <div className="edit-controls">
<AutoForm <AutoForm
@ -2509,10 +2599,16 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
model={selectedMetricSpec} model={selectedMetricSpec}
onChange={selectedMetricSpec => this.setState({ selectedMetricSpec })} onChange={selectedMetricSpec => this.setState({ selectedMetricSpec })}
/> />
{selectedMetricSpecIndex !== -1 && (
<FormGroup>
<Popover content={convertToDimensionMenu}>
<Button icon={IconNames.EXCHANGE} text="Convert to dimension..." />
</Popover>
</FormGroup>
)}
<div className="control-buttons"> <div className="control-buttons">
<Button <Button
className="add-update" text="Apply"
text={selectedMetricSpecIndex === -1 ? 'Add' : 'Update'}
intent={Intent.PRIMARY} intent={Intent.PRIMARY}
onClick={() => { onClick={() => {
this.updateSpec( this.updateSpec(
@ -2525,8 +2621,10 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
close(); close();
}} }}
/> />
<Button text="Cancel" onClick={close} />
{selectedMetricSpecIndex !== -1 && ( {selectedMetricSpecIndex !== -1 && (
<Button <Button
className="right"
icon={IconNames.TRASH} icon={IconNames.TRASH}
intent={Intent.DANGER} intent={Intent.DANGER}
onClick={() => { onClick={() => {
@ -2537,7 +2635,6 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
}} }}
/> />
)} )}
<Button className="cancel" text="Cancel" onClick={close} />
</div> </div>
</div> </div>
); );
@ -2633,7 +2730,9 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
<Callout className="intro"> <Callout className="intro">
<p className="optional">Optional</p> <p className="optional">Optional</p>
<p>Configure how Druid will partition data.</p> <p>Configure how Druid will partition data.</p>
<LearnMore href="https://druid.apache.org/docs/latest/ingestion/index.html#partitioning" /> <LearnMore
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/ingestion/index.html#partitioning`}
/>
</Callout> </Callout>
{this.renderParallelPickerIfNeeded()} {this.renderParallelPickerIfNeeded()}
</div> </div>
@ -2698,7 +2797,9 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
<Callout className="intro"> <Callout className="intro">
<p className="optional">Optional</p> <p className="optional">Optional</p>
<p>Fine tune how Druid will ingest data.</p> <p>Fine tune how Druid will ingest data.</p>
<LearnMore href="https://druid.apache.org/docs/latest/ingestion/index.html#tuningconfig" /> <LearnMore
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/ingestion/index.html#tuningconfig`}
/>
</Callout> </Callout>
{this.renderParallelPickerIfNeeded()} {this.renderParallelPickerIfNeeded()}
</div> </div>