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

View File

@ -16,20 +16,12 @@
* limitations under the License.
*/
import {
Button,
ButtonGroup,
FormGroup,
Icon,
Intent,
NumericInput,
Popover,
} from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import { Button, ButtonGroup, FormGroup, Intent, NumericInput } from '@blueprintjs/core';
import React from 'react';
import { deepDelete, deepGet, deepSet } from '../../utils/object-change';
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 { 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);
return (
<FormGroup
<FormGroupWithInfo
key={field.name}
label={label}
labelInfo={
field.info && (
<Popover
content={<div className="label-info-text">{field.info}</div>}
position="left-bottom"
>
<Icon icon={IconNames.INFO_SIGN} iconSize={14} />
</Popover>
)
}
info={field.info ? <div className="label-info-text">{field.info}</div> : undefined}
>
{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
disabled={false}
href="https://druid.apache.org/docs/latest"
href="https://druid.apache.org/docs/0.16.0-incubating"
icon="th"
multiline={false}
popoverProps={Object {}}

View File

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

View File

@ -20,6 +20,7 @@ import { Button, Classes, Dialog, Intent } from '@blueprintjs/core';
import React from 'react';
import { AutoForm, ExternalLink } from '../../components';
import { DRUID_DOCS_VERSION } from '../../variables';
import './compaction-dialog.scss';
@ -125,7 +126,9 @@ export class CompactionDialog extends React.PureComponent<
type: 'json',
info: (
<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
</ExternalLink>{' '}
for compaction tasks.
@ -143,7 +146,9 @@ export class CompactionDialog extends React.PureComponent<
type: 'json',
info: (
<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
</ExternalLink>{' '}
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
<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"
target="_blank"
>

View File

@ -24,6 +24,7 @@ import React from 'react';
import { AutoForm, ExternalLink } from '../../components';
import { AppToaster } from '../../singletons/toaster';
import { getDruidErrorMessage, QueryManager } from '../../utils';
import { DRUID_DOCS_VERSION } from '../../variables';
import { SnitchDialog } from '../snitch-dialog/snitch-dialog';
import './coordinator-dynamic-config-dialog.scss';
@ -126,7 +127,9 @@ export class CoordinatorDynamicConfigDialog extends React.PureComponent<
<p>
Edit the coordinator dynamic configuration on the fly. For more information please refer
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
</ExternalLink>
.

View File

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

View File

@ -16,7 +16,8 @@
* 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 { JsonCollapse } from '../../components';
@ -25,54 +26,50 @@ import './history-dialog.scss';
interface HistoryDialogProps {
historyRecords: any[];
children?: ReactNode;
buttons?: ReactNode;
}
export const HistoryDialog = React.memo(function HistoryDialog(props: HistoryDialogProps) {
function renderRecords() {
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);
const { buttons, historyRecords } = props;
return (
<div key={i} className="history-record-entry">
<Card>
<div className="history-record-title">
<span className="history-record-title-change">Change</span>
<span>{formattedTime}</span>
</div>
<Divider />
<p>{auditInfo.comment === '' ? '(No comment)' : auditInfo.comment}</p>
<JsonCollapse stringValue={record.payload} buttonText="Payload" />
</Card>
</div>
);
})}
</div>
</>
);
}
return (
<div className="history-record-container">
{content}
{children}
</div>
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 (
<div key={i} className="history-record-entry">
<Card>
<div className="history-record-title">
<span className="history-record-title-change">Change</span>
<span>{formattedTime}</span>
</div>
<Divider />
<p>{auditInfo.comment === '' ? '(No comment)' : auditInfo.comment}</p>
<JsonCollapse stringValue={record.payload} buttonText="Payload" />
</Card>
</div>
);
})}
</div>
</>
);
}
return (
<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>
);
});

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
<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"
target="_blank"
>

View File

@ -24,6 +24,7 @@ import React from 'react';
import { AutoForm, ExternalLink } from '../../components';
import { AppToaster } from '../../singletons/toaster';
import { getDruidErrorMessage, QueryManager } from '../../utils';
import { DRUID_DOCS_VERSION } from '../../variables';
import { SnitchDialog } from '../snitch-dialog/snitch-dialog';
import './overlord-dynamic-config-dialog.scss';
@ -129,7 +130,9 @@ export class OverlordDynamicConfigDialog extends React.PureComponent<
<p>
Edit the overlord dynamic configuration on the fly. For more information please refer to
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
</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
<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"
>
documentation

View File

@ -23,6 +23,7 @@ import React from 'react';
import { RuleEditor } from '../../components';
import { QueryManager } from '../../utils';
import { DRUID_DOCS_VERSION } from '../../variables';
import { SnitchDialog } from '../snitch-dialog/snitch-dialog';
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
evaluated in order from top to bottom. For more information please refer to the{' '}
<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"
>
documentation

View File

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

View File

@ -62,17 +62,3 @@ svg {
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 { ExternalLink } from '../components/external-link/external-link';
import { DRUID_DOCS_VERSION } from '../variables';
import {
BASIC_TIME_FORMATS,
@ -162,13 +163,13 @@ export function getIngestionDocLink(spec: IngestionSpec): string {
switch (type) {
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':
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:
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>
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
</ExternalLink>
.
@ -573,7 +576,9 @@ const FLATTEN_FIELD_FORM_FIELDS: Field<FlattenField>[] = [
info: (
<>
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
</ExternalLink>
.
@ -618,7 +623,9 @@ const TRANSFORM_FORM_FIELDS: Field<Transform>[] = [
info: (
<>
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
</ExternalLink>
.
@ -820,7 +827,9 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
info: (
<p>
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
</ExternalLink>
. You can change your selected firehose here.
@ -873,7 +882,9 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
required: true,
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
</ExternalLink>
<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'],
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
</ExternalLink>
<p>
@ -963,7 +976,9 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
info: (
<p>
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
</ExternalLink>{' '}
to apply to the data as part of querying.
@ -1026,7 +1041,9 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
<>
<p>
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
</ExternalLink>
.
@ -1057,7 +1074,9 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
required: true,
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
</ExternalLink>
<p>
@ -1079,7 +1098,9 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
defaultValue: {},
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
</ExternalLink>
<p>A map of properties to be passed to the Kafka consumer.</p>
@ -1129,7 +1150,9 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
info: (
<>
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
</ExternalLink>
.

View File

@ -19,13 +19,16 @@
export const LEGACY_COORDINATOR_CONSOLE = '/index.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_GITHUB = 'https://github.com/apache/druid';
export const DRUID_DOCS = 'https://druid.apache.org/docs/latest';
export const DRUID_DOCS_SQL = 'https://druid.apache.org/docs/latest/querying/sql.html';
export const DRUID_DOCS_RUNE = 'https://druid.apache.org/docs/latest/querying/querying.html';
export const DRUID_DOCS = `https://druid.apache.org/docs/${DRUID_DOCS_VERSION}`;
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/${DRUID_DOCS_VERSION}/querying/querying.html`;
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_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_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"
>
<a
href="https://druid.apache.org/docs/latest/development/extensions-core/kinesis-ingestion.html"
href="https://druid.apache.org/docs"
rel="noopener noreferrer"
target="_blank"
>

View File

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

View File

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

View File

@ -31,6 +31,8 @@ import {
Icon,
IconName,
Intent,
Menu,
MenuItem,
Popover,
Switch,
TextArea,
@ -49,6 +51,7 @@ import {
JsonInput,
Loader,
} from '../../components';
import { FormGroupWithInfo } from '../../components/form-group-with-info/form-group-with-info';
import { AsyncActionDialog } from '../../dialogs';
import { AppToaster } from '../../singletons/toaster';
import { UrlBaser } from '../../singletons/url-baser';
@ -140,6 +143,7 @@ import {
SampleStrategy,
} from '../../utils/sampler';
import { computeFlattenPathsForData } from '../../utils/spec-utils';
import { DRUID_DOCS_VERSION } from '../../variables';
import { ExamplePicker } from './example-picker/example-picker';
import { FilterTable, filterTableSelectedColumnName } from './filter-table/filter-table';
@ -555,7 +559,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
const previewSpecSame = this.isPreviewSpecSame();
return (
<FormGroup className="apply-button-bar">
<FormGroup className="control-buttons">
<Button
text="Apply"
disabled={previewSpecSame}
@ -564,8 +568,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
/>
{!previewSpecSame && (
<Button
className="revert"
icon={IconNames.UNDO}
text="Cancel"
disabled={this.isPreviewSpecSame()}
onClick={this.revertPreviewSpec}
/>
@ -791,7 +794,9 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
return (
<p>
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
</ExternalLink>
.
@ -893,7 +898,9 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
</p>
<p>
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
</ExternalLink>
.
@ -1029,7 +1036,9 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
<Callout className="intro">
<p>
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
</ExternalLink>{' '}
that is optimized for analytic queries.
@ -1264,7 +1273,9 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
{canFlatten && (
<p>
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
</ExternalLink>{' '}
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>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>
{!selectedFlattenField && (
<>
@ -1362,8 +1375,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
/>
<div className="control-buttons">
<Button
className="add-update"
text={selectedFlattenFieldIndex === -1 ? 'Add' : 'Update'}
text="Apply"
intent={Intent.PRIMARY}
onClick={() => {
this.updateSpec(
@ -1376,8 +1388,10 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
close();
}}
/>
<Button text="Cancel" onClick={close} />
{selectedFlattenFieldIndex !== -1 && (
<Button
className="right"
icon={IconNames.TRASH}
intent={Intent.DANGER}
onClick={() => {
@ -1391,7 +1405,6 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
}}
/>
)}
<Button className="cancel" text="Cancel" onClick={close} />
</div>
</div>
);
@ -1409,7 +1422,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
/>
<AnchorButton
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"
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
a default one.
</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>
<FormGroup label="Timestamp spec">
<ButtonGroup>
@ -1692,12 +1707,16 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
<p className="optional">Optional</p>
<p>
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
</ExternalLink>{' '}
of column values allowing you to create new derived columns or alter existing column.
</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>
{Boolean(transformQueryState.error && transforms.length) && (
<FormGroup>
@ -1758,8 +1777,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
/>
<div className="control-buttons">
<Button
className="add-update"
text={selectedTransformIndex === -1 ? 'Add' : 'Update'}
text="Apply"
intent={Intent.PRIMARY}
onClick={() => {
this.updateSpec(
@ -1772,8 +1790,10 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
close();
}}
/>
<Button text="Cancel" onClick={close} />
{selectedTransformIndex !== -1 && (
<Button
className="right"
icon={IconNames.TRASH}
intent={Intent.DANGER}
onClick={() => {
@ -1787,7 +1807,6 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
}}
/>
)}
<Button className="cancel" text="Cancel" onClick={close} />
</div>
</div>
);
@ -1935,12 +1954,16 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
<p className="optional">Optional</p>
<p>
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
</ExternalLink>{' '}
out unwanted data by applying per-row filters.
</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>
{!showGlobalFilter && this.renderColumnFilterControls()}
{!selectedFilter && this.renderGlobalFilterControls()}
@ -1982,8 +2005,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
/>
<div className="control-buttons">
<Button
className="add-update"
text={selectedFilterIndex === -1 ? 'Add' : 'Update'}
text="Apply"
intent={Intent.PRIMARY}
onClick={() => {
const curFilter = splitFilter(deepGet(spec, 'dataSchema.transformSpec.filter'));
@ -1994,8 +2016,10 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
close();
}}
/>
<Button text="Cancel" onClick={close} />
{selectedFilterIndex !== -1 && (
<Button
className="right"
icon={IconNames.TRASH}
intent={Intent.DANGER}
onClick={() => {
@ -2008,7 +2032,6 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
}}
/>
)}
<Button className="cancel" text="Cancel" onClick={close} />
</div>
</div>
);
@ -2068,17 +2091,8 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
/>
</FormGroup>
<div className="control-buttons">
<Button
className="add-update"
text="Apply"
intent={Intent.PRIMARY}
onClick={() => this.queryForFilter()}
/>
<Button
className="cancel"
text="Close"
onClick={() => this.setState({ showGlobalFilter: false })}
/>
<Button text="Apply" intent={Intent.PRIMARY} onClick={() => this.queryForFilter()} />
<Button text="Cancel" onClick={() => this.setState({ showGlobalFilter: false })} />
</div>
</div>
);
@ -2205,11 +2219,36 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
want to change the type, click on the column header.
</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>
{!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
checked={dimensionMode === 'specific'}
onChange={() =>
@ -2219,29 +2258,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
}
label="Explicitly specify dimension list"
/>
<Popover
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>
</FormGroupWithInfo>
{dimensionMode === 'auto-detect' && (
<AutoForm
fields={[
@ -2261,43 +2278,45 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
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
checked={rollup}
onChange={() => this.setState({ newRollup: !rollup })}
labelElement="Rollup"
/>
<Popover
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>
</FormGroupWithInfo>
<AutoForm
fields={[
{
@ -2425,6 +2444,33 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
const curDimensions =
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 (
<div className="edit-controls">
<AutoForm
@ -2432,10 +2478,20 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
model={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">
<Button
className="add-update"
text={selectedDimensionSpecIndex === -1 ? 'Add' : 'Update'}
text="Apply"
intent={Intent.PRIMARY}
onClick={() => {
this.updateSpec(
@ -2448,8 +2504,10 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
close();
}}
/>
<Button text="Cancel" onClick={close} />
{selectedDimensionSpecIndex !== -1 && (
<Button
className="right"
icon={IconNames.TRASH}
intent={Intent.DANGER}
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>
);
@ -2502,6 +2559,39 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
};
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 (
<div className="edit-controls">
<AutoForm
@ -2509,10 +2599,16 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
model={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">
<Button
className="add-update"
text={selectedMetricSpecIndex === -1 ? 'Add' : 'Update'}
text="Apply"
intent={Intent.PRIMARY}
onClick={() => {
this.updateSpec(
@ -2525,8 +2621,10 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
close();
}}
/>
<Button text="Cancel" onClick={close} />
{selectedMetricSpecIndex !== -1 && (
<Button
className="right"
icon={IconNames.TRASH}
intent={Intent.DANGER}
onClick={() => {
@ -2537,7 +2635,6 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
}}
/>
)}
<Button className="cancel" text="Cancel" onClick={close} />
</div>
</div>
);
@ -2633,7 +2730,9 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
<Callout className="intro">
<p className="optional">Optional</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>
{this.renderParallelPickerIfNeeded()}
</div>
@ -2698,7 +2797,9 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
<Callout className="intro">
<p className="optional">Optional</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>
{this.renderParallelPickerIfNeeded()}
</div>