You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@druid.apache.org by fj...@apache.org on 2019/09/03 23:11:26 UTC
[incubator-druid] branch 0.16.0-incubating updated: Web console:
Fix segment re-ingest (#8454) (#8457)
This is an automated email from the ASF dual-hosted git repository.
fjy pushed a commit to branch 0.16.0-incubating
in repository https://gitbox.apache.org/repos/asf/incubator-druid.git
The following commit(s) were added to refs/heads/0.16.0-incubating by this push:
new 00dd600 Web console: Fix segment re-ingest (#8454) (#8457)
00dd600 is described below
commit 00dd600160b4b395a09ee75a48994bac07f7c6aa
Author: Clint Wylie <cw...@apache.org>
AuthorDate: Tue Sep 3 16:11:12 2019 -0700
Web console: Fix segment re-ingest (#8454) (#8457)
* fixing ingest spec
* rm console.log
* ingest segment spec
* make sure step query always being run
* better example interface
* add keywords
* placeholders
* do not overwrite datasource name in data loader spec
* fix comment typo
---
web-console/lib/keywords.js | 8 +-
web-console/package-lock.json | 6 +-
web-console/package.json | 2 +-
.../datasource-columns-table.tsx | 52 ++---
.../src/components/json-input/json-input.tsx | 8 +-
.../src/components/rule-editor/rule-editor.tsx | 2 +
web-console/src/components/show-json/show-json.tsx | 2 +-
.../supervisor-statistics-table.tsx | 13 +-
...coordinator-dynamic-config-dialog.spec.tsx.snap | 1 -
.../coordinator-dynamic-config-dialog.tsx | 1 -
.../datasource-table-action-dialog.spec.tsx | 1 -
.../datasource-table-action-dialog.tsx | 4 +-
.../edit-context-dialog.spec.tsx | 2 +-
.../__snapshots__/history-dialog.spec.tsx.snap | 2 +-
.../dialogs/history-dialog/history-dialog.spec.tsx | 1 -
.../src/dialogs/history-dialog/history-dialog.tsx | 6 +-
.../lookup-edit-dialog/lookup-edit-dialog.spec.tsx | 1 -
.../lookup-edit-dialog/lookup-edit-dialog.tsx | 4 +-
.../overload-dynamic-config-dialog.spec.tsx.snap | 1 -
.../overlord-dynamic-config-dialog.tsx | 1 -
.../dialogs/retention-dialog/retention-dialog.tsx | 2 -
.../segment-table-action-dialog.spec.tsx | 1 -
.../segment-table-action-dialog.tsx | 4 +-
.../__snapshots__/snitch-dialog.spec.tsx.snap | 35 ++-
.../dialogs/snitch-dialog/snitch-dialog.spec.tsx | 2 +-
.../src/dialogs/snitch-dialog/snitch-dialog.tsx | 19 +-
.../__snapshots__/status-dialog.spec.tsx.snap | 134 ++++--------
.../dialogs/status-dialog/status-dialog.spec.tsx | 10 +-
.../src/dialogs/status-dialog/status-dialog.tsx | 12 +-
.../supervisor-table-action-dialog.spec.tsx | 3 +-
.../supervisor-table-action-dialog.tsx | 4 +-
.../table-action-dialog.spec.tsx.snap | 34 +++
.../table-action-dialog.spec.tsx | 2 +-
.../table-action-dialog/table-action-dialog.tsx | 18 +-
.../task-table-action-dialog.spec.tsx | 1 -
.../task-table-action-dialog.tsx | 4 +-
web-console/src/entry.scss | 2 +
web-console/src/entry.ts | 2 +-
web-console/src/singletons/url-baser.ts | 4 +-
web-console/src/utils/general.tsx | 4 +-
web-console/src/utils/ingestion-spec.tsx | 36 +++-
web-console/src/utils/sampler.ts | 130 ++++++++---
.../src/views/datasource-view/datasource-view.tsx | 1 -
.../views/home-view/status-card/status-card.tsx | 79 ++++---
.../__snapshots__/example-picker.spec.tsx.snap | 56 +++++
.../example-picker/example-picker.spec.tsx} | 21 +-
.../example-picker/example-picker.tsx | 77 +++++++
.../src/views/load-data-view/load-data-view.tsx | 240 ++++++++++++++-------
.../__snapshots__/lookups-view.spec.tsx.snap | 12 --
.../src/views/lookups-view/lookups-view.tsx | 16 +-
.../src/views/segments-view/segments-view.tsx | 1 -
web-console/src/views/task-view/tasks-view.tsx | 2 -
52 files changed, 697 insertions(+), 389 deletions(-)
diff --git a/web-console/lib/keywords.js b/web-console/lib/keywords.js
index 85e6b7d..c7261cd 100644
--- a/web-console/lib/keywords.js
+++ b/web-console/lib/keywords.js
@@ -71,4 +71,10 @@ exports.SQL_EXPRESSION_PARTS = [
exports.SQL_CONSTANTS = ['NULL', 'FALSE', 'TRUE'];
-exports.SQL_DYNAMICS = ['CURRENT_TIMESTAMP', 'CURRENT_DATE'];
+exports.SQL_DYNAMICS = [
+ 'CURRENT_TIMESTAMP',
+ 'CURRENT_DATE',
+ 'LOCALTIME',
+ 'LOCALTIMESTAMP',
+ 'CURRENT_TIME',
+];
diff --git a/web-console/package-lock.json b/web-console/package-lock.json
index a4ae431..1fbb606 100644
--- a/web-console/package-lock.json
+++ b/web-console/package-lock.json
@@ -4428,9 +4428,9 @@
"integrity": "sha512-0sYnfUHHMoajaud/i5BHKA12bUxiWEHJ9rxGqVEppFxsEcxef0TZQ5J59lU+UniEBcz/sG5fTESRyS7cOm3tSQ=="
},
"druid-query-toolkit": {
- "version": "0.3.26",
- "resolved": "https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.3.26.tgz",
- "integrity": "sha512-j9HcwHCx2YnFSefYc1oJDw8rPq5zSB0tpGkaMp2GkO9syKbdncKfUPugZ613c5XIOBe+j5Hqh/luqh4sLacHGQ==",
+ "version": "0.3.27",
+ "resolved": "https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.3.27.tgz",
+ "integrity": "sha512-dGqTU3x9V1zPHAwng47hDihwKhx1UBJbIBJsOtFmlgb+D69iDcQVingPsjJOV+kHX2gVq/Azlhx6MZvC7+5tFQ==",
"requires": {
"tslib": "^1.10.0"
}
diff --git a/web-console/package.json b/web-console/package.json
index 553938f..9cb57a5 100644
--- a/web-console/package.json
+++ b/web-console/package.json
@@ -61,7 +61,7 @@
"d3": "^5.10.1",
"d3-array": "^2.3.1",
"druid-console": "0.0.2",
- "druid-query-toolkit": "^0.3.26",
+ "druid-query-toolkit": "^0.3.27",
"file-saver": "^2.0.2",
"has-own-prop": "^2.0.0",
"hjson": "^3.1.2",
diff --git a/web-console/src/components/datasource-columns-table/datasource-columns-table.tsx b/web-console/src/components/datasource-columns-table/datasource-columns-table.tsx
index d494fb9..983a97a 100644
--- a/web-console/src/components/datasource-columns-table/datasource-columns-table.tsx
+++ b/web-console/src/components/datasource-columns-table/datasource-columns-table.tsx
@@ -17,16 +17,16 @@
*/
import React from 'react';
-import ReactTable, { Column } from 'react-table';
+import ReactTable from 'react-table';
-import { Loader } from '..';
import { queryDruidSql, QueryManager } from '../../utils';
import { ColumnMetadata } from '../../utils/column-metadata';
+import { Loader } from '../loader/loader';
import './datasource-columns-table.scss';
interface TableRow {
- columnsName: string;
+ columnName: string;
columnType: string;
}
@@ -36,7 +36,7 @@ export interface DatasourceColumnsTableProps {
}
export interface DatasourceColumnsTableState {
- columns?: any;
+ columns?: TableRow[];
loading: boolean;
error?: string;
}
@@ -45,24 +45,26 @@ export class DatasourceColumnsTable extends React.PureComponent<
DatasourceColumnsTableProps,
DatasourceColumnsTableState
> {
- private supervisorStatisticsTableQueryManager: QueryManager<null, TableRow[]>;
+ private datasourceColumnsQueryManager: QueryManager<null, TableRow[]>;
constructor(props: DatasourceColumnsTableProps, context: any) {
super(props, context);
this.state = {
loading: true,
};
- this.supervisorStatisticsTableQueryManager = new QueryManager({
+
+ this.datasourceColumnsQueryManager = new QueryManager({
processQuery: async () => {
const { datasourceId } = this.props;
+
const resp = await queryDruidSql<ColumnMetadata>({
query: `SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'druid' AND TABLE_NAME = '${datasourceId}'`,
});
- const dimensionArray = resp.map(object => {
- return { columnsName: object.COLUMN_NAME, columnType: object.DATA_TYPE };
+
+ return resp.map(object => {
+ return { columnName: object.COLUMN_NAME, columnType: object.DATA_TYPE };
});
- return dimensionArray;
},
onStateChange: ({ result, error, loading }) => {
this.setState({ columns: result, error, loading });
@@ -71,30 +73,28 @@ export class DatasourceColumnsTable extends React.PureComponent<
}
componentDidMount(): void {
- this.supervisorStatisticsTableQueryManager.runQuery(null);
+ this.datasourceColumnsQueryManager.runQuery(null);
}
renderTable(error?: string) {
const { columns } = this.state;
- console.log(columns);
- const tableColumns: Column<TableRow>[] = [
- {
- Header: 'Column Name',
- accessor: 'columnsName',
- },
- {
- Header: 'Data Type',
- accessor: 'columnType',
- },
- ];
return (
<ReactTable
- data={this.state.columns ? this.state.columns : []}
- showPagination={false}
- defaultPageSize={15}
- columns={tableColumns}
- noDataText={error ? error : 'No statistics data found'}
+ data={columns || []}
+ defaultPageSize={20}
+ filterable
+ columns={[
+ {
+ Header: 'Column name',
+ accessor: 'columnName',
+ },
+ {
+ Header: 'Data type',
+ accessor: 'columnType',
+ },
+ ]}
+ noDataText={error ? error : 'No column data found'}
/>
);
}
diff --git a/web-console/src/components/json-input/json-input.tsx b/web-console/src/components/json-input/json-input.tsx
index c8d13a3..756ee16 100644
--- a/web-console/src/components/json-input/json-input.tsx
+++ b/web-console/src/components/json-input/json-input.tsx
@@ -19,7 +19,7 @@
import React from 'react';
import AceEditor from 'react-ace';
-import { parseStringToJSON, stringifyJSON, validJson } from '../../utils';
+import { parseStringToJson, stringifyJson, validJson } from '../../utils';
interface JSONInputProps {
onChange: (newJSONValue: any) => void;
@@ -45,7 +45,7 @@ export class JSONInput extends React.PureComponent<JSONInputProps, JSONInputStat
componentDidMount(): void {
const { value } = this.props;
- const stringValue = stringifyJSON(value);
+ const stringValue = stringifyJson(value);
this.setState({
stringValue,
});
@@ -54,7 +54,7 @@ export class JSONInput extends React.PureComponent<JSONInputProps, JSONInputStat
componentWillReceiveProps(nextProps: JSONInputProps): void {
if (JSON.stringify(nextProps.value) !== JSON.stringify(this.props.value)) {
this.setState({
- stringValue: stringifyJSON(nextProps.value),
+ stringValue: stringifyJson(nextProps.value),
});
}
}
@@ -70,7 +70,7 @@ export class JSONInput extends React.PureComponent<JSONInputProps, JSONInputStat
name="ace-editor"
onChange={(e: string) => {
this.setState({ stringValue: e });
- if (validJson(e) || e === '') onChange(parseStringToJSON(e));
+ if (validJson(e) || e === '') onChange(parseStringToJson(e));
if (updateInputValidity) updateInputValidity(validJson(e) || e === '');
}}
focus={focus}
diff --git a/web-console/src/components/rule-editor/rule-editor.tsx b/web-console/src/components/rule-editor/rule-editor.tsx
index 6ff916a..03882be 100644
--- a/web-console/src/components/rule-editor/rule-editor.tsx
+++ b/web-console/src/components/rule-editor/rule-editor.tsx
@@ -295,6 +295,7 @@ export class RuleEditor extends React.PureComponent<RuleEditorProps, RuleEditorS
onChange={(e: any) =>
onChange(RuleEditor.changePeriod(rule, e.target.value as any))
}
+ placeholder="P1D"
/>
)}
{ruleTimeType === 'ByInterval' && (
@@ -303,6 +304,7 @@ export class RuleEditor extends React.PureComponent<RuleEditorProps, RuleEditorS
onChange={(e: any) =>
onChange(RuleEditor.changeInterval(rule, e.target.value as any))
}
+ placeholder="2010-01-01/2020-01-01"
/>
)}
</ControlGroup>
diff --git a/web-console/src/components/show-json/show-json.tsx b/web-console/src/components/show-json/show-json.tsx
index 15fcdde..b8282bf 100644
--- a/web-console/src/components/show-json/show-json.tsx
+++ b/web-console/src/components/show-json/show-json.tsx
@@ -93,7 +93,7 @@ export class ShowJson extends React.PureComponent<ShowJsonProps, ShowJsonState>
onClick={() => {
copy(jsonValue ? jsonValue : '', { format: 'text/plain' });
AppToaster.show({
- message: 'JSON copied to clipboard',
+ message: 'JSON value copied to clipboard',
intent: Intent.SUCCESS,
});
}}
diff --git a/web-console/src/components/supervisor-statistics-table/supervisor-statistics-table.tsx b/web-console/src/components/supervisor-statistics-table/supervisor-statistics-table.tsx
index 5c5ab32..67afd0a 100644
--- a/web-console/src/components/supervisor-statistics-table/supervisor-statistics-table.tsx
+++ b/web-console/src/components/supervisor-statistics-table/supervisor-statistics-table.tsx
@@ -21,10 +21,10 @@ import axios from 'axios';
import React from 'react';
import ReactTable, { Column } from 'react-table';
-import { Loader } from '..';
import { UrlBaser } from '../../singletons/url-baser';
import { QueryManager } from '../../utils';
import { deepGet } from '../../utils/object-change';
+import { Loader } from '../loader/loader';
import './supervisor-statistics-table.scss';
@@ -61,14 +61,15 @@ export class SupervisorStatisticsTable extends React.PureComponent<
SupervisorStatisticsTableProps,
SupervisorStatisticsTableState
> {
- private supervisorStatisticsTableQueryManager: QueryManager<null, TableRow[]>;
+ private supervisorStatisticsQueryManager: QueryManager<null, TableRow[]>;
constructor(props: SupervisorStatisticsTableProps, context: any) {
super(props, context);
this.state = {
loading: true,
};
- this.supervisorStatisticsTableQueryManager = new QueryManager({
+
+ this.supervisorStatisticsQueryManager = new QueryManager({
processQuery: async () => {
const { endpoint } = this.props;
const resp = await axios.get(endpoint);
@@ -89,7 +90,7 @@ export class SupervisorStatisticsTable extends React.PureComponent<
}
componentDidMount(): void {
- this.supervisorStatisticsTableQueryManager.runQuery(null);
+ this.supervisorStatisticsQueryManager.runQuery(null);
}
renderCell(data: StatsEntry | undefined) {
@@ -103,10 +104,10 @@ export class SupervisorStatisticsTable extends React.PureComponent<
renderTable(error?: string) {
const { data } = this.state;
- console.log(data);
+
let columns: Column<TableRow>[] = [
{
- Header: 'Task Id',
+ Header: 'Task ID',
id: 'task_id',
accessor: d => d.taskId,
},
diff --git a/web-console/src/dialogs/coordinator-dynamic-config-dialog/__snapshots__/coordinator-dynamic-config-dialog.spec.tsx.snap b/web-console/src/dialogs/coordinator-dynamic-config-dialog/__snapshots__/coordinator-dynamic-config-dialog.spec.tsx.snap
index 05e80ff..c66c987 100644
--- a/web-console/src/dialogs/coordinator-dynamic-config-dialog/__snapshots__/coordinator-dynamic-config-dialog.spec.tsx.snap
+++ b/web-console/src/dialogs/coordinator-dynamic-config-dialog/__snapshots__/coordinator-dynamic-config-dialog.spec.tsx.snap
@@ -9,7 +9,6 @@ exports[`coordinator dynamic config matches snapshot 1`] = `
>
<div
class="bp3-overlay-backdrop bp3-overlay-appear bp3-overlay-appear-active"
- tabindex="0"
/>
<div
class="bp3-dialog-container bp3-overlay-content bp3-overlay-appear bp3-overlay-appear-active"
diff --git a/web-console/src/dialogs/coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog.tsx b/web-console/src/dialogs/coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog.tsx
index 18818ca..181eb27 100644
--- a/web-console/src/dialogs/coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog.tsx
+++ b/web-console/src/dialogs/coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog.tsx
@@ -118,7 +118,6 @@ export class CoordinatorDynamicConfigDialog extends React.PureComponent<
return (
<SnitchDialog
className="coordinator-dynamic-config-dialog"
- isOpen
onSave={this.saveClusterConfig}
onClose={onClose}
title="Coordinator dynamic config"
diff --git a/web-console/src/dialogs/datasource-table-action-dialog/datasource-table-action-dialog.spec.tsx b/web-console/src/dialogs/datasource-table-action-dialog/datasource-table-action-dialog.spec.tsx
index c6f0723..0f6d19e 100644
--- a/web-console/src/dialogs/datasource-table-action-dialog/datasource-table-action-dialog.spec.tsx
+++ b/web-console/src/dialogs/datasource-table-action-dialog/datasource-table-action-dialog.spec.tsx
@@ -28,7 +28,6 @@ describe('Datasource table action dialog', () => {
datasourceId="test"
actions={[{ title: 'test', onAction: () => null }]}
onClose={() => {}}
- isOpen
/>
);
render(datasourceTableActionDialog);
diff --git a/web-console/src/dialogs/datasource-table-action-dialog/datasource-table-action-dialog.tsx b/web-console/src/dialogs/datasource-table-action-dialog/datasource-table-action-dialog.tsx
index 8289216..c604f14 100644
--- a/web-console/src/dialogs/datasource-table-action-dialog/datasource-table-action-dialog.tsx
+++ b/web-console/src/dialogs/datasource-table-action-dialog/datasource-table-action-dialog.tsx
@@ -16,14 +16,13 @@
* limitations under the License.
*/
-import { IDialogProps } from '@blueprintjs/core';
import React from 'react';
import { DatasourceColumnsTable } from '../../components/datasource-columns-table/datasource-columns-table';
import { BasicAction } from '../../utils/basic-action';
import { SideButtonMetaData, TableActionDialog } from '../table-action-dialog/table-action-dialog';
-interface DatasourceTableActionDialogProps extends IDialogProps {
+interface DatasourceTableActionDialogProps {
datasourceId?: string;
actions: BasicAction[];
onClose: () => void;
@@ -59,7 +58,6 @@ export class DatasourceTableActionDialog extends React.PureComponent<
return (
<TableActionDialog
- isOpen
sideButtonMetadata={taskTableSideButtonMetadata}
onClose={onClose}
title={`Datasource: ${datasourceId}`}
diff --git a/web-console/src/dialogs/edit-context-dialog/edit-context-dialog.spec.tsx b/web-console/src/dialogs/edit-context-dialog/edit-context-dialog.spec.tsx
index 602bc2e..9d760d6 100644
--- a/web-console/src/dialogs/edit-context-dialog/edit-context-dialog.spec.tsx
+++ b/web-console/src/dialogs/edit-context-dialog/edit-context-dialog.spec.tsx
@@ -24,7 +24,7 @@ import { EditContextDialog } from './edit-context-dialog';
describe('clipboard dialog', () => {
it('matches snapshot', () => {
const compactionDialog = (
- <EditContextDialog queryContext={{}} onQueryContextChange={() => null} onClose={() => null} />
+ <EditContextDialog queryContext={{}} onQueryContextChange={() => null} onClose={() => {}} />
);
render(compactionDialog);
expect(document.body.lastChild).toMatchSnapshot();
diff --git a/web-console/src/dialogs/history-dialog/__snapshots__/history-dialog.spec.tsx.snap b/web-console/src/dialogs/history-dialog/__snapshots__/history-dialog.spec.tsx.snap
index a39d629..fdf97e9 100644
--- a/web-console/src/dialogs/history-dialog/__snapshots__/history-dialog.spec.tsx.snap
+++ b/web-console/src/dialogs/history-dialog/__snapshots__/history-dialog.spec.tsx.snap
@@ -16,7 +16,7 @@ exports[`history dialog matches snapshot 1`] = `
tabindex="0"
>
<div
- class="bp3-dialog"
+ class="bp3-dialog history-dialog"
>
<div
class="history-record-container"
diff --git a/web-console/src/dialogs/history-dialog/history-dialog.spec.tsx b/web-console/src/dialogs/history-dialog/history-dialog.spec.tsx
index 176f35a..9ed39ee 100644
--- a/web-console/src/dialogs/history-dialog/history-dialog.spec.tsx
+++ b/web-console/src/dialogs/history-dialog/history-dialog.spec.tsx
@@ -29,7 +29,6 @@ describe('history dialog', () => {
{ auditTime: 'test', auditInfo: 'test', payload: JSON.stringify({ name: 'test' }) },
{ auditTime: 'test', auditInfo: 'test', payload: JSON.stringify({ name: 'test' }) },
]}
- isOpen
/>
);
render(historyDialog);
diff --git a/web-console/src/dialogs/history-dialog/history-dialog.tsx b/web-console/src/dialogs/history-dialog/history-dialog.tsx
index 9553c9b..389d9fa 100644
--- a/web-console/src/dialogs/history-dialog/history-dialog.tsx
+++ b/web-console/src/dialogs/history-dialog/history-dialog.tsx
@@ -16,14 +16,14 @@
* limitations under the License.
*/
-import { Card, Dialog, Divider, IDialogProps } from '@blueprintjs/core';
+import { Card, Dialog, Divider } from '@blueprintjs/core';
import React from 'react';
import { JSONCollapse } from '../../components';
import './history-dialog.scss';
-interface HistoryDialogProps extends IDialogProps {
+interface HistoryDialogProps {
historyRecords: any[];
}
@@ -71,7 +71,7 @@ export class HistoryDialog extends React.PureComponent<HistoryDialogProps> {
render(): React.ReactNode {
return (
- <Dialog isOpen {...this.props}>
+ <Dialog className="history-dialog" isOpen {...this.props}>
{this.renderRecords()}
</Dialog>
);
diff --git a/web-console/src/dialogs/lookup-edit-dialog/lookup-edit-dialog.spec.tsx b/web-console/src/dialogs/lookup-edit-dialog/lookup-edit-dialog.spec.tsx
index 0793637..b243f14 100644
--- a/web-console/src/dialogs/lookup-edit-dialog/lookup-edit-dialog.spec.tsx
+++ b/web-console/src/dialogs/lookup-edit-dialog/lookup-edit-dialog.spec.tsx
@@ -25,7 +25,6 @@ describe('lookup edit dialog', () => {
it('matches snapshot', () => {
const lookupEditDialog = (
<LookupEditDialog
- isOpen
onClose={() => {}}
onSubmit={() => {}}
onChange={() => {}}
diff --git a/web-console/src/dialogs/lookup-edit-dialog/lookup-edit-dialog.tsx b/web-console/src/dialogs/lookup-edit-dialog/lookup-edit-dialog.tsx
index c7a19af..1c79639 100644
--- a/web-console/src/dialogs/lookup-edit-dialog/lookup-edit-dialog.tsx
+++ b/web-console/src/dialogs/lookup-edit-dialog/lookup-edit-dialog.tsx
@@ -33,7 +33,6 @@ import { validJson } from '../../utils';
import './lookup-edit-dialog.scss';
export interface LookupEditDialogProps {
- isOpen: boolean;
onClose: () => void;
onSubmit: () => void;
onChange: (field: string, value: string) => void;
@@ -86,7 +85,6 @@ export class LookupEditDialog extends React.PureComponent<LookupEditDialogProps>
render(): JSX.Element {
const {
- isOpen,
onClose,
onSubmit,
lookupSpec,
@@ -103,7 +101,7 @@ export class LookupEditDialog extends React.PureComponent<LookupEditDialogProps>
return (
<Dialog
className="lookup-edit-dialog"
- isOpen={isOpen}
+ isOpen
onClose={onClose}
title={isEdit ? 'Edit lookup' : 'Add lookup'}
>
diff --git a/web-console/src/dialogs/overlord-dynamic-config-dialog/__snapshots__/overload-dynamic-config-dialog.spec.tsx.snap b/web-console/src/dialogs/overlord-dynamic-config-dialog/__snapshots__/overload-dynamic-config-dialog.spec.tsx.snap
index d913e34..c5b2326 100644
--- a/web-console/src/dialogs/overlord-dynamic-config-dialog/__snapshots__/overload-dynamic-config-dialog.spec.tsx.snap
+++ b/web-console/src/dialogs/overlord-dynamic-config-dialog/__snapshots__/overload-dynamic-config-dialog.spec.tsx.snap
@@ -9,7 +9,6 @@ exports[`overload dynamic config matches snapshot 1`] = `
>
<div
class="bp3-overlay-backdrop bp3-overlay-appear bp3-overlay-appear-active"
- tabindex="0"
/>
<div
class="bp3-dialog-container bp3-overlay-content bp3-overlay-appear bp3-overlay-appear-active"
diff --git a/web-console/src/dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog.tsx b/web-console/src/dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog.tsx
index ad0ea17..b94d0d8 100644
--- a/web-console/src/dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog.tsx
+++ b/web-console/src/dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog.tsx
@@ -120,7 +120,6 @@ export class OverlordDynamicConfigDialog extends React.PureComponent<
return (
<SnitchDialog
className="overlord-dynamic-config-dialog"
- isOpen
onSave={this.saveConfig}
onClose={onClose}
title="Overlord dynamic config"
diff --git a/web-console/src/dialogs/retention-dialog/retention-dialog.tsx b/web-console/src/dialogs/retention-dialog/retention-dialog.tsx
index 790ff06..4783757 100644
--- a/web-console/src/dialogs/retention-dialog/retention-dialog.tsx
+++ b/web-console/src/dialogs/retention-dialog/retention-dialog.tsx
@@ -171,8 +171,6 @@ export class RetentionDialog extends React.PureComponent<
<SnitchDialog
className="retention-dialog"
saveDisabled={false}
- canOutsideClickClose={false}
- isOpen
onClose={onCancel}
title={`Edit retention rules: ${datasource}${
datasource === '_default' ? ' (cluster defaults)' : ''
diff --git a/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.spec.tsx b/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.spec.tsx
index 46e4c57..dd31379 100644
--- a/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.spec.tsx
+++ b/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.spec.tsx
@@ -29,7 +29,6 @@ describe('task table action dialog', () => {
segmentId="test"
actions={[{ title: 'test', onAction: () => null }]}
onClose={() => {}}
- isOpen
/>
);
render(taskTableActionDialog);
diff --git a/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.tsx b/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.tsx
index 8a952e5..3271101 100644
--- a/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.tsx
+++ b/web-console/src/dialogs/segments-table-action-dialog/segment-table-action-dialog.tsx
@@ -16,14 +16,13 @@
* limitations under the License.
*/
-import { IDialogProps } from '@blueprintjs/core';
import React from 'react';
import { ShowJson } from '../../components';
import { BasicAction } from '../../utils/basic-action';
import { SideButtonMetaData, TableActionDialog } from '../table-action-dialog/table-action-dialog';
-interface SegmentTableActionDialogProps extends IDialogProps {
+interface SegmentTableActionDialogProps {
segmentId?: string;
datasourceId?: string;
actions: BasicAction[];
@@ -60,7 +59,6 @@ export class SegmentTableActionDialog extends React.PureComponent<
return (
<TableActionDialog
- isOpen
sideButtonMetadata={taskTableSideButtonMetadata}
onClose={onClose}
title={`Segment: ${segmentId}`}
diff --git a/web-console/src/dialogs/snitch-dialog/__snapshots__/snitch-dialog.spec.tsx.snap b/web-console/src/dialogs/snitch-dialog/__snapshots__/snitch-dialog.spec.tsx.snap
index 47092ba..d223804 100644
--- a/web-console/src/dialogs/snitch-dialog/__snapshots__/snitch-dialog.spec.tsx.snap
+++ b/web-console/src/dialogs/snitch-dialog/__snapshots__/snitch-dialog.spec.tsx.snap
@@ -9,7 +9,6 @@ exports[`snitch dialog matches snapshot 1`] = `
>
<div
class="bp3-overlay-backdrop bp3-overlay-appear bp3-overlay-appear-active"
- tabindex="0"
/>
<div
class="bp3-dialog-container bp3-overlay-content bp3-overlay-appear bp3-overlay-appear-active"
@@ -19,6 +18,40 @@ exports[`snitch dialog matches snapshot 1`] = `
class="bp3-dialog snitch-dialog"
>
<div
+ class="bp3-dialog-header"
+ >
+ <h4
+ class="bp3-heading"
+ >
+ Be snitchin
+ </h4>
+ <button
+ aria-label="Close"
+ class="bp3-button bp3-minimal bp3-dialog-close-button"
+ type="button"
+ >
+ <span
+ class="bp3-icon bp3-icon-small-cross"
+ icon="small-cross"
+ >
+ <svg
+ data-icon="small-cross"
+ height="20"
+ viewBox="0 0 20 20"
+ width="20"
+ >
+ <desc>
+ small-cross
+ </desc>
+ <path
+ d="M11.41 10l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L10 8.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42L8.59 10 5.3 13.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71l3.29-3.3 3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L11.41 10z"
+ fill-rule="evenodd"
+ />
+ </svg>
+ </span>
+ </button>
+ </div>
+ <div
class="bp3-dialog-body"
/>
<div
diff --git a/web-console/src/dialogs/snitch-dialog/snitch-dialog.spec.tsx b/web-console/src/dialogs/snitch-dialog/snitch-dialog.spec.tsx
index 1c73826..ff3a5c6 100644
--- a/web-console/src/dialogs/snitch-dialog/snitch-dialog.spec.tsx
+++ b/web-console/src/dialogs/snitch-dialog/snitch-dialog.spec.tsx
@@ -23,7 +23,7 @@ import { SnitchDialog } from './snitch-dialog';
describe('snitch dialog', () => {
it('matches snapshot', () => {
- const snitchDialog = <SnitchDialog onSave={() => {}} isOpen />;
+ const snitchDialog = <SnitchDialog title="Be snitchin" onSave={() => {}} onClose={() => {}} />;
render(snitchDialog);
expect(document.body.lastChild).toMatchSnapshot();
});
diff --git a/web-console/src/dialogs/snitch-dialog/snitch-dialog.tsx b/web-console/src/dialogs/snitch-dialog/snitch-dialog.tsx
index fc8b4f4..985d16d 100644
--- a/web-console/src/dialogs/snitch-dialog/snitch-dialog.tsx
+++ b/web-console/src/dialogs/snitch-dialog/snitch-dialog.tsx
@@ -16,15 +16,7 @@
* limitations under the License.
*/
-import {
- Button,
- Classes,
- Dialog,
- FormGroup,
- IDialogProps,
- InputGroup,
- Intent,
-} from '@blueprintjs/core';
+import { Button, Classes, Dialog, FormGroup, InputGroup, Intent } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import classNames from 'classnames';
import React from 'react';
@@ -33,10 +25,13 @@ import { HistoryDialog } from '../history-dialog/history-dialog';
import './snitch-dialog.scss';
-export interface SnitchDialogProps extends IDialogProps {
+export interface SnitchDialogProps {
+ title: string;
+ className?: string;
onSave: (comment: string) => void;
saveDisabled?: boolean;
onReset?: () => void;
+ onClose: () => void;
historyRecords?: any[];
}
@@ -121,7 +116,7 @@ export class SnitchDialog extends React.PureComponent<SnitchDialogProps, SnitchD
if (!historyRecords) return null;
return (
- <HistoryDialog {...this.props} className="history-dialog" historyRecords={historyRecords}>
+ <HistoryDialog {...this.props} historyRecords={historyRecords}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button onClick={this.back} icon={IconNames.ARROW_LEFT}>
Back
@@ -187,7 +182,7 @@ export class SnitchDialog extends React.PureComponent<SnitchDialogProps, SnitchD
const propsClone: any = Object.assign({}, this.props);
propsClone.className = classNames('snitch-dialog', propsClone.className);
return (
- <Dialog isOpen {...propsClone}>
+ <Dialog isOpen {...propsClone} canOutsideClickClose={false}>
<div className={Classes.DIALOG_BODY}>{children}</div>
<div className={Classes.DIALOG_FOOTER}>{this.renderActions(saveDisabled)}</div>
</Dialog>
diff --git a/web-console/src/dialogs/status-dialog/__snapshots__/status-dialog.spec.tsx.snap b/web-console/src/dialogs/status-dialog/__snapshots__/status-dialog.spec.tsx.snap
index a215194..004c512 100644
--- a/web-console/src/dialogs/status-dialog/__snapshots__/status-dialog.spec.tsx.snap
+++ b/web-console/src/dialogs/status-dialog/__snapshots__/status-dialog.spec.tsx.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`table action dialog matches snapshot 1`] = `
+exports[`status dialog matches snapshot 1`] = `
<div
class="bp3-portal"
>
@@ -9,13 +9,14 @@ exports[`table action dialog matches snapshot 1`] = `
>
<div
class="bp3-overlay-backdrop bp3-overlay-appear bp3-overlay-appear-active"
+ tabindex="0"
/>
<div
class="bp3-dialog-container bp3-overlay-content bp3-overlay-appear bp3-overlay-appear-active"
tabindex="0"
>
<div
- class="bp3-dialog spec-dialog"
+ class="bp3-dialog status-dialog"
>
<div
class="bp3-dialog-header"
@@ -23,7 +24,7 @@ exports[`table action dialog matches snapshot 1`] = `
<h4
class="bp3-heading"
>
- spec-dialog
+ Status
</h4>
<button
aria-label="Close"
@@ -52,92 +53,59 @@ exports[`table action dialog matches snapshot 1`] = `
</button>
</div>
<div
- class=" ace_editor ace-tm spec-dialog-textarea"
- id="brace-editor"
- style="width: 100%; height: 500px;"
+ class="status-dialog-main-area"
>
- <textarea
- autocapitalize="off"
- autocorrect="off"
- class="ace_text-input"
- spellcheck="false"
- style="opacity: 0;"
- wrap="off"
- />
<div
- aria-hidden="true"
- class="ace_gutter"
+ class="show-json"
>
<div
- class="ace_layer ace_gutter-layer ace_folding-enabled"
- />
- <div
- class="ace_gutter-active-line"
- />
- </div>
- <div
- class="ace_scroller"
- >
- <div
- class="ace_content"
+ class="top-actions"
>
<div
- class="ace_layer ace_print-margin-layer"
+ class="bp3-button-group right-buttons"
>
- <div
- class="ace_print-margin"
- style="left: 4px; visibility: hidden;"
- />
- </div>
- <div
- class="ace_layer ace_marker-layer"
- />
- <div
- class="ace_layer ace_text-layer"
- style="padding: 0px 4px;"
- />
- <div
- class="ace_layer ace_marker-layer"
- />
- <div
- class="ace_layer ace_cursor-layer ace_hidden-cursors"
- >
- <div
- class="ace_cursor"
- />
+ <button
+ class="bp3-button bp3-disabled bp3-minimal"
+ disabled=""
+ tabindex="-1"
+ type="button"
+ >
+ <span
+ class="bp3-button-text"
+ >
+ Save
+ </span>
+ </button>
+ <button
+ class="bp3-button bp3-disabled bp3-minimal"
+ disabled=""
+ tabindex="-1"
+ type="button"
+ >
+ <span
+ class="bp3-button-text"
+ >
+ Copy
+ </span>
+ </button>
+ <button
+ class="bp3-button bp3-disabled bp3-minimal"
+ disabled=""
+ tabindex="-1"
+ type="button"
+ >
+ <span
+ class="bp3-button-text"
+ >
+ View raw
+ </span>
+ </button>
</div>
</div>
- </div>
- <div
- class="ace_scrollbar ace_scrollbar-v"
- style="display: none; width: 20px;"
- >
- <div
- class="ace_scrollbar-inner"
- style="width: 20px;"
- />
- </div>
- <div
- class="ace_scrollbar ace_scrollbar-h"
- style="display: none; height: 20px;"
- >
<div
- class="ace_scrollbar-inner"
- style="height: 20px;"
+ class="main-area"
/>
</div>
- <div
- style="height: auto; width: auto; top: 0px; left: 0px; visibility: hidden; position: absolute; white-space: pre; overflow: hidden;"
- >
- <div
- style="height: auto; width: auto; top: 0px; left: 0px; visibility: hidden; position: absolute; white-space: pre; overflow: visible;"
- />
- <div
- style="height: auto; width: auto; top: 0px; left: 0px; visibility: hidden; position: absolute; white-space: pre; overflow: visible;"
- >
- XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
- </div>
- </div>
</div>
<div
class="bp3-dialog-footer"
@@ -146,23 +114,13 @@ exports[`table action dialog matches snapshot 1`] = `
class="bp3-dialog-footer-actions"
>
<button
- class="bp3-button"
- type="button"
- >
- <span
- class="bp3-button-text"
- >
- Close
- </span>
- </button>
- <button
class="bp3-button bp3-intent-primary"
type="button"
>
<span
class="bp3-button-text"
>
- Submit
+ Close
</span>
</button>
</div>
diff --git a/web-console/src/dialogs/status-dialog/status-dialog.spec.tsx b/web-console/src/dialogs/status-dialog/status-dialog.spec.tsx
index 625eb93..d323665 100644
--- a/web-console/src/dialogs/status-dialog/status-dialog.spec.tsx
+++ b/web-console/src/dialogs/status-dialog/status-dialog.spec.tsx
@@ -19,14 +19,12 @@
import { render } from '@testing-library/react';
import React from 'react';
-import { SpecDialog } from '..';
+import { StatusDialog } from './status-dialog';
-describe('table action dialog', () => {
+describe('status dialog', () => {
it('matches snapshot', () => {
- const tableActionDialog = (
- <SpecDialog onClose={() => null} title={'spec-dialog'} onSubmit={() => null} />
- );
- render(tableActionDialog);
+ const statusDialog = <StatusDialog onClose={() => {}} />;
+ render(statusDialog);
expect(document.body.lastChild).toMatchSnapshot();
});
});
diff --git a/web-console/src/dialogs/status-dialog/status-dialog.tsx b/web-console/src/dialogs/status-dialog/status-dialog.tsx
index 2f3e43d..0785f66 100644
--- a/web-console/src/dialogs/status-dialog/status-dialog.tsx
+++ b/web-console/src/dialogs/status-dialog/status-dialog.tsx
@@ -16,26 +16,26 @@
* limitations under the License.
*/
-import { Button, Classes, Dialog, IDialogProps, Intent } from '@blueprintjs/core';
+import { Button, Classes, Dialog, Intent } from '@blueprintjs/core';
import React from 'react';
import { ShowJson } from '../../components';
+import { UrlBaser } from '../../singletons/url-baser';
import './status-dialog.scss';
-interface StatusDialogProps extends IDialogProps {
+interface StatusDialogProps {
onClose: () => void;
- title: string;
}
export class StatusDialog extends React.PureComponent<StatusDialogProps> {
render(): JSX.Element {
- const { onClose, title, isOpen } = this.props;
+ const { onClose } = this.props;
return (
- <Dialog className={'status-dialog'} onClose={onClose} isOpen={isOpen} title={title}>
+ <Dialog className={'status-dialog'} onClose={onClose} isOpen title="Status">
<div className={'status-dialog-main-area'}>
- <ShowJson endpoint={`/status`} downloadFilename={'status'} />
+ <ShowJson endpoint={UrlBaser.base(`/status`)} downloadFilename={'status'} />
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
diff --git a/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.spec.tsx b/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.spec.tsx
index 18ef377..9c4c05a 100644
--- a/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.spec.tsx
+++ b/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.spec.tsx
@@ -21,7 +21,7 @@ import React from 'react';
import { SupervisorTableActionDialog } from './supervisor-table-action-dialog';
-const basicAction = { title: 'test', onAction: () => null };
+const basicAction = { title: 'test', onAction: () => {} };
describe('supervisor table action dialog', () => {
it('matches snapshot', () => {
@@ -30,7 +30,6 @@ describe('supervisor table action dialog', () => {
supervisorId={'test'}
actions={[basicAction, basicAction, basicAction, basicAction]}
onClose={() => {}}
- isOpen
/>
);
render(supervisorTableActionDialog);
diff --git a/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.tsx b/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.tsx
index 8720b78..2ea3927 100644
--- a/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.tsx
+++ b/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.tsx
@@ -16,7 +16,6 @@
* limitations under the License.
*/
-import { IDialogProps } from '@blueprintjs/core';
import React from 'react';
import { ShowJson } from '../../components';
@@ -26,7 +25,7 @@ import { BasicAction } from '../../utils/basic-action';
import { deepGet } from '../../utils/object-change';
import { SideButtonMetaData, TableActionDialog } from '../table-action-dialog/table-action-dialog';
-interface SupervisorTableActionDialogProps extends IDialogProps {
+interface SupervisorTableActionDialogProps {
supervisorId: string;
actions: BasicAction[];
onClose: () => void;
@@ -79,7 +78,6 @@ export class SupervisorTableActionDialog extends React.PureComponent<
return (
<TableActionDialog
- isOpen
sideButtonMetadata={supervisorTableSideButtonMetadata}
onClose={onClose}
title={`Supervisor: ${supervisorId}`}
diff --git a/web-console/src/dialogs/table-action-dialog/__snapshots__/table-action-dialog.spec.tsx.snap b/web-console/src/dialogs/table-action-dialog/__snapshots__/table-action-dialog.spec.tsx.snap
index ec333f3..315dc75 100644
--- a/web-console/src/dialogs/table-action-dialog/__snapshots__/table-action-dialog.spec.tsx.snap
+++ b/web-console/src/dialogs/table-action-dialog/__snapshots__/table-action-dialog.spec.tsx.snap
@@ -19,6 +19,40 @@ exports[`table action dialog matches snapshot 1`] = `
class="bp3-dialog table-action-dialog"
>
<div
+ class="bp3-dialog-header"
+ >
+ <h4
+ class="bp3-heading"
+ >
+ Table dummy actions
+ </h4>
+ <button
+ aria-label="Close"
+ class="bp3-button bp3-minimal bp3-dialog-close-button"
+ type="button"
+ >
+ <span
+ class="bp3-icon bp3-icon-small-cross"
+ icon="small-cross"
+ >
+ <svg
+ data-icon="small-cross"
+ height="20"
+ viewBox="0 0 20 20"
+ width="20"
+ >
+ <desc>
+ small-cross
+ </desc>
+ <path
+ d="M11.41 10l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L10 8.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42L8.59 10 5.3 13.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71l3.29-3.3 3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L11.41 10z"
+ fill-rule="evenodd"
+ />
+ </svg>
+ </span>
+ </button>
+ </div>
+ <div
class="bp3-dialog-body"
>
<div
diff --git a/web-console/src/dialogs/table-action-dialog/table-action-dialog.spec.tsx b/web-console/src/dialogs/table-action-dialog/table-action-dialog.spec.tsx
index 5e5ff8b..0de6017 100644
--- a/web-console/src/dialogs/table-action-dialog/table-action-dialog.spec.tsx
+++ b/web-console/src/dialogs/table-action-dialog/table-action-dialog.spec.tsx
@@ -25,9 +25,9 @@ describe('table action dialog', () => {
it('matches snapshot', () => {
const tableActionDialog = (
<TableActionDialog
+ title="Table dummy actions"
sideButtonMetadata={[{ icon: 'badge', text: 'test' }]}
onClose={() => {}}
- isOpen
/>
);
render(tableActionDialog);
diff --git a/web-console/src/dialogs/table-action-dialog/table-action-dialog.tsx b/web-console/src/dialogs/table-action-dialog/table-action-dialog.tsx
index b7c04a3..f128495 100644
--- a/web-console/src/dialogs/table-action-dialog/table-action-dialog.tsx
+++ b/web-console/src/dialogs/table-action-dialog/table-action-dialog.tsx
@@ -16,16 +16,7 @@
* limitations under the License.
*/
-import {
- Button,
- Classes,
- Dialog,
- Icon,
- IconName,
- IDialogProps,
- Intent,
- Popover,
-} from '@blueprintjs/core';
+import { Button, Classes, Dialog, Icon, IconName, Intent, Popover } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import React from 'react';
@@ -40,7 +31,8 @@ export interface SideButtonMetaData {
onClick?: () => void;
}
-interface TableActionDialogProps extends IDialogProps {
+interface TableActionDialogProps {
+ title: string;
sideButtonMetadata: SideButtonMetaData[];
onClose: () => void;
actions?: BasicAction[];
@@ -48,11 +40,11 @@ interface TableActionDialogProps extends IDialogProps {
export class TableActionDialog extends React.PureComponent<TableActionDialogProps> {
render(): JSX.Element {
- const { sideButtonMetadata, isOpen, onClose, title, actions, children } = this.props;
+ const { sideButtonMetadata, onClose, title, actions, children } = this.props;
const actionsMenu = actions ? basicActionsToMenu(actions) : undefined;
return (
- <Dialog className="table-action-dialog" isOpen={isOpen} onClose={onClose} title={title}>
+ <Dialog className="table-action-dialog" isOpen onClose={onClose} title={title}>
<div className={Classes.DIALOG_BODY}>
<div className="side-bar">
{sideButtonMetadata.map((d, i) => (
diff --git a/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.spec.tsx b/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.spec.tsx
index adaacd6..c35840a 100644
--- a/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.spec.tsx
+++ b/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.spec.tsx
@@ -30,7 +30,6 @@ describe('task table action dialog', () => {
taskId={'test'}
actions={[basicAction]}
onClose={() => {}}
- isOpen
/>
);
render(taskTableActionDialog);
diff --git a/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.tsx b/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.tsx
index 87987be..351bdb4 100644
--- a/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.tsx
+++ b/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.tsx
@@ -16,7 +16,6 @@
* limitations under the License.
*/
-import { IDialogProps } from '@blueprintjs/core';
import React from 'react';
import { ShowJson, ShowLog } from '../../components';
@@ -24,7 +23,7 @@ import { BasicAction } from '../../utils/basic-action';
import { deepGet } from '../../utils/object-change';
import { SideButtonMetaData, TableActionDialog } from '../table-action-dialog/table-action-dialog';
-interface TaskTableActionDialogProps extends IDialogProps {
+interface TaskTableActionDialogProps {
taskId: string;
actions: BasicAction[];
onClose: () => void;
@@ -79,7 +78,6 @@ export class TaskTableActionDialog extends React.PureComponent<
return (
<TableActionDialog
- isOpen
sideButtonMetadata={taskTableSideButtonMetadata}
onClose={onClose}
title={`Task: ${taskId}`}
diff --git a/web-console/src/entry.scss b/web-console/src/entry.scss
index fcea2e2..8e36392 100644
--- a/web-console/src/entry.scss
+++ b/web-console/src/entry.scss
@@ -24,7 +24,9 @@
html,
body {
//font-family: 'Open Sans', Helvetica, Arial, sans-serif;
+ position: fixed;
height: 100%;
+ width: 100%;
overflow: hidden;
font-size: 13px;
}
diff --git a/web-console/src/entry.ts b/web-console/src/entry.ts
index 1f52c29..dc124e8 100644
--- a/web-console/src/entry.ts
+++ b/web-console/src/entry.ts
@@ -53,7 +53,7 @@ if (typeof consoleConfig.title === 'string') {
if (consoleConfig.baseURL) {
axios.defaults.baseURL = consoleConfig.baseURL;
- UrlBaser.baseURL = consoleConfig.baseURL;
+ UrlBaser.baseUrl = consoleConfig.baseURL;
}
if (consoleConfig.customHeaderName && consoleConfig.customHeaderValue) {
axios.defaults.headers.common[consoleConfig.customHeaderName] = consoleConfig.customHeaderValue;
diff --git a/web-console/src/singletons/url-baser.ts b/web-console/src/singletons/url-baser.ts
index df0cc6b..1cc9ac7 100644
--- a/web-console/src/singletons/url-baser.ts
+++ b/web-console/src/singletons/url-baser.ts
@@ -17,10 +17,10 @@
*/
export class UrlBaser {
- static baseURL: string = '';
+ static baseUrl: string = '';
static base(url: string): string {
if (!url.startsWith('/')) return url;
- return UrlBaser.baseURL + url;
+ return UrlBaser.baseUrl + url;
}
}
diff --git a/web-console/src/utils/general.tsx b/web-console/src/utils/general.tsx
index 27c79c6..6444392 100644
--- a/web-console/src/utils/general.tsx
+++ b/web-console/src/utils/general.tsx
@@ -261,7 +261,7 @@ export function validJson(json: string): boolean {
}
// stringify JSON to string; if JSON is null, parse empty string ""
-export function stringifyJSON(item: any): string {
+export function stringifyJson(item: any): string {
if (item != null) {
return JSON.stringify(item, null, 2);
} else {
@@ -270,7 +270,7 @@ export function stringifyJSON(item: any): string {
}
// parse string to JSON object; if string is empty, return null
-export function parseStringToJSON(s: string): JSON | null {
+export function parseStringToJson(s: string): JSON | null {
if (s === '') {
return null;
} else {
diff --git a/web-console/src/utils/ingestion-spec.tsx b/web-console/src/utils/ingestion-spec.tsx
index 30a1903..27f88b0 100644
--- a/web-console/src/utils/ingestion-spec.tsx
+++ b/web-console/src/utils/ingestion-spec.tsx
@@ -229,6 +229,10 @@ export function getSpecType(spec: Partial<IngestionSpec>): IngestionType | undef
);
}
+export function isIngestSegment(spec: IngestionSpec): boolean {
+ return deepGet(spec, 'ioConfig.firehose.type') === 'ingestSegment';
+}
+
export function changeParallel(spec: IngestionSpec, parallel: boolean): IngestionSpec {
if (!hasParallelAbility(spec)) return spec;
const newType = parallel ? 'index_parallel' : 'index';
@@ -777,14 +781,26 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
placeholder:
'https://example.com/path/to/file1.ext, https://example.com/path/to/file2.ext',
info: (
- <>
- <p>
- The full URI of your file. To ingest from multiple URIs, use commas to separate each
- individual URI.
- </p>
- </>
+ <p>
+ The full URI of your file. To ingest from multiple URIs, use commas to separate each
+ individual URI.
+ </p>
),
},
+ {
+ name: 'firehose.httpAuthenticationUsername',
+ label: 'HTTP auth username',
+ type: 'string',
+ placeholder: '(optional)',
+ info: <p>Username to use for authentication with specified URIs</p>,
+ },
+ {
+ name: 'firehose.httpAuthenticationPassword',
+ label: 'HTTP auth password',
+ type: 'string',
+ placeholder: '(optional)',
+ info: <p>Password to use for authentication with specified URIs</p>,
+ },
];
case 'index:local':
@@ -841,9 +857,9 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
type: 'string',
placeholder: `${CURRENT_YEAR}-01-01/${CURRENT_YEAR + 1}-01-01`,
suggestions: [
- `${CURRENT_YEAR}/${CURRENT_YEAR + 1}`,
- `${CURRENT_YEAR}-01-01/${CURRENT_YEAR + 1}-01-01`,
`${CURRENT_YEAR}-01-01T00:00:00/${CURRENT_YEAR + 1}-01-01T00:00:00`,
+ `${CURRENT_YEAR}-01-01/${CURRENT_YEAR + 1}-01-01`,
+ `${CURRENT_YEAR}/${CURRENT_YEAR + 1}`,
],
info: (
<p>
@@ -1439,7 +1455,9 @@ function basenameFromFilename(filename: string): string | undefined {
return filename.split('.')[0];
}
-export function fillDataSourceName(spec: IngestionSpec): IngestionSpec {
+export function fillDataSourceNameIfNeeded(spec: IngestionSpec): IngestionSpec {
+ // Do not overwrite if the spec already has a name
+ if (deepGet(spec, 'dataSchema.dataSource')) return spec;
const ioConfig = deepGet(spec, 'ioConfig');
if (!ioConfig) return spec;
const possibleName = guessDataSourceName(ioConfig);
diff --git a/web-console/src/utils/sampler.ts b/web-console/src/utils/sampler.ts
index bd33dd2..7f7e9f1 100644
--- a/web-console/src/utils/sampler.ts
+++ b/web-console/src/utils/sampler.ts
@@ -18,7 +18,7 @@
import axios from 'axios';
-import { getDruidErrorMessage } from './druid-query';
+import { getDruidErrorMessage, queryDruidRune } from './druid-query';
import { alphanumericCompare, filterMap, sortWithPrefixSuffix } from './general';
import {
DimensionsSpec,
@@ -27,6 +27,7 @@ import {
IngestionSpec,
IoConfig,
isColumnTimestampSpec,
+ isIngestSegment,
MetricSpec,
Parser,
ParseSpec,
@@ -35,6 +36,8 @@ import {
} from './ingestion-spec';
import { deepGet, deepSet, whitelistKeys } from './object-change';
+const MS_IN_HALF_HOUR = 30 * 60 * 1000;
+
const SAMPLER_URL = `/druid/indexer/v1/sampler`;
const BASE_SAMPLER_CONFIG: SamplerConfig = {
// skipCache: true,
@@ -60,6 +63,14 @@ export interface SampleResponse {
data: SampleEntry[];
}
+export interface SampleResponseWithExtraInfo extends SampleResponse {
+ queryGranularity?: any;
+ timestampSpec?: any;
+ rollup?: boolean;
+ columns?: Record<string, any>;
+ aggregators?: Record<string, any>;
+}
+
export interface SampleEntry {
raw: string;
parsed?: Record<string, any>;
@@ -167,17 +178,66 @@ function makeSamplerIoConfig(
return ioConfig;
}
+/**
+ * This function scopes down the interval of an ingestSegment firehose for the data sampler
+ * this is needed because the ingestSegment firehose gets the interval you are sampling over,
+ * looks up the corresponding segments and segment locations from metadata store, downloads
+ * every segment from deep storage to disk, and then maps all the segments into memory;
+ * and this happens in the constructor before the timer thread is even created meaning the sampler
+ * will time out on a larger interval.
+ * This is essentially a workaround for https://github.com/apache/incubator-druid/issues/8448
+ * @param ioConfig The IO Config to scope down the interval of
+ */
+export async function scopeDownIngestSegmentFirehoseIntervalIfNeeded(
+ ioConfig: IoConfig,
+): Promise<IoConfig> {
+ if (deepGet(ioConfig, 'firehose.type') !== 'ingestSegment') return ioConfig;
+ const interval = deepGet(ioConfig, 'firehose.interval');
+ const intervalParts = interval.split('/');
+ const start = new Date(intervalParts[0]);
+ if (isNaN(start.valueOf())) throw new Error(`could not decode interval start`);
+ const end = new Date(intervalParts[1]);
+ if (isNaN(end.valueOf())) throw new Error(`could not decode interval end`);
+
+ // Less than or equal to 1/2 hour so there is no need to adjust intervals
+ if (Math.abs(end.valueOf() - start.valueOf()) <= MS_IN_HALF_HOUR) return ioConfig;
+
+ const dataSourceMetadataResponse = await queryDruidRune({
+ queryType: 'dataSourceMetadata',
+ dataSource: deepGet(ioConfig, 'firehose.dataSource'),
+ });
+
+ const maxIngestedEventTime = new Date(
+ deepGet(dataSourceMetadataResponse, '0.result.maxIngestedEventTime'),
+ );
+
+ // If invalid maxIngestedEventTime do nothing
+ if (isNaN(maxIngestedEventTime.valueOf())) return ioConfig;
+
+ // If maxIngestedEventTime is before the start of the interval do nothing
+ if (maxIngestedEventTime < start) return ioConfig;
+
+ const newEnd = maxIngestedEventTime < end ? maxIngestedEventTime : end;
+ const newStart = new Date(newEnd.valueOf() - MS_IN_HALF_HOUR); // Set start to 1/2hr ago
+
+ return deepSet(
+ ioConfig,
+ 'firehose.interval',
+ `${newStart.toISOString()}/${newEnd.toISOString()}`,
+ );
+}
+
export async function sampleForConnect(
spec: IngestionSpec,
sampleStrategy: SampleStrategy,
-): Promise<SampleResponse> {
+): Promise<SampleResponseWithExtraInfo> {
const samplerType = getSamplerType(spec);
- const ioConfig: IoConfig = makeSamplerIoConfig(
- deepGet(spec, 'ioConfig'),
- samplerType,
- sampleStrategy,
+ const ioConfig: IoConfig = await scopeDownIngestSegmentFirehoseIntervalIfNeeded(
+ makeSamplerIoConfig(deepGet(spec, 'ioConfig'), samplerType, sampleStrategy),
);
+ const ingestSegmentMode = isIngestSegment(spec);
+
const sampleSpec: SampleSpec = {
type: samplerType,
spec: {
@@ -200,7 +260,33 @@ export async function sampleForConnect(
samplerConfig: BASE_SAMPLER_CONFIG,
};
- return postToSampler(sampleSpec, 'connect');
+ const samplerResponse: SampleResponseWithExtraInfo = await postToSampler(sampleSpec, 'connect');
+
+ if (!samplerResponse.data.length) return samplerResponse;
+
+ if (ingestSegmentMode) {
+ const segmentMetadataResponse = await queryDruidRune({
+ queryType: 'segmentMetadata',
+ dataSource: deepGet(ioConfig, 'firehose.dataSource'),
+ intervals: [deepGet(ioConfig, 'firehose.interval')],
+ merge: true,
+ lenientAggregatorMerge: true,
+ analysisTypes: ['timestampSpec', 'queryGranularity', 'aggregators', 'rollup'],
+ });
+
+ if (Array.isArray(segmentMetadataResponse) && segmentMetadataResponse.length === 1) {
+ const segmentMetadataResponse0 = segmentMetadataResponse[0];
+ samplerResponse.queryGranularity = segmentMetadataResponse0.queryGranularity;
+ samplerResponse.timestampSpec = segmentMetadataResponse0.timestampSpec;
+ samplerResponse.rollup = segmentMetadataResponse0.rollup;
+ samplerResponse.columns = segmentMetadataResponse0.columns;
+ samplerResponse.aggregators = segmentMetadataResponse0.aggregators;
+ } else {
+ throw new Error(`unexpected response from segmentMetadata query`);
+ }
+ }
+
+ return samplerResponse;
}
export async function sampleForParser(
@@ -209,10 +295,8 @@ export async function sampleForParser(
cacheKey: string | undefined,
): Promise<SampleResponse> {
const samplerType = getSamplerType(spec);
- const ioConfig: IoConfig = makeSamplerIoConfig(
- deepGet(spec, 'ioConfig'),
- samplerType,
- sampleStrategy,
+ const ioConfig: IoConfig = await scopeDownIngestSegmentFirehoseIntervalIfNeeded(
+ makeSamplerIoConfig(deepGet(spec, 'ioConfig'), samplerType, sampleStrategy),
);
const parser: Parser = deepGet(spec, 'dataSchema.parser') || {};
@@ -248,10 +332,8 @@ export async function sampleForTimestamp(
cacheKey: string | undefined,
): Promise<SampleResponse> {
const samplerType = getSamplerType(spec);
- const ioConfig: IoConfig = makeSamplerIoConfig(
- deepGet(spec, 'ioConfig'),
- samplerType,
- sampleStrategy,
+ const ioConfig: IoConfig = await scopeDownIngestSegmentFirehoseIntervalIfNeeded(
+ makeSamplerIoConfig(deepGet(spec, 'ioConfig'), samplerType, sampleStrategy),
);
const parser: Parser = deepGet(spec, 'dataSchema.parser') || {};
const parseSpec: ParseSpec = deepGet(spec, 'dataSchema.parser.parseSpec') || {};
@@ -339,10 +421,8 @@ export async function sampleForTransform(
cacheKey: string | undefined,
): Promise<SampleResponse> {
const samplerType = getSamplerType(spec);
- const ioConfig: IoConfig = makeSamplerIoConfig(
- deepGet(spec, 'ioConfig'),
- samplerType,
- sampleStrategy,
+ const ioConfig: IoConfig = await scopeDownIngestSegmentFirehoseIntervalIfNeeded(
+ makeSamplerIoConfig(deepGet(spec, 'ioConfig'), samplerType, sampleStrategy),
);
const parser: Parser = deepGet(spec, 'dataSchema.parser') || {};
const parseSpec: ParseSpec = deepGet(spec, 'dataSchema.parser.parseSpec') || {};
@@ -415,10 +495,8 @@ export async function sampleForFilter(
cacheKey: string | undefined,
): Promise<SampleResponse> {
const samplerType = getSamplerType(spec);
- const ioConfig: IoConfig = makeSamplerIoConfig(
- deepGet(spec, 'ioConfig'),
- samplerType,
- sampleStrategy,
+ const ioConfig: IoConfig = await scopeDownIngestSegmentFirehoseIntervalIfNeeded(
+ makeSamplerIoConfig(deepGet(spec, 'ioConfig'), samplerType, sampleStrategy),
);
const parser: Parser = deepGet(spec, 'dataSchema.parser') || {};
const parseSpec: ParseSpec = deepGet(spec, 'dataSchema.parser.parseSpec') || {};
@@ -493,10 +571,8 @@ export async function sampleForSchema(
cacheKey: string | undefined,
): Promise<SampleResponse> {
const samplerType = getSamplerType(spec);
- const ioConfig: IoConfig = makeSamplerIoConfig(
- deepGet(spec, 'ioConfig'),
- samplerType,
- sampleStrategy,
+ const ioConfig: IoConfig = await scopeDownIngestSegmentFirehoseIntervalIfNeeded(
+ makeSamplerIoConfig(deepGet(spec, 'ioConfig'), samplerType, sampleStrategy),
);
const parser: Parser = deepGet(spec, 'dataSchema.parser') || {};
const transformSpec: TransformSpec =
diff --git a/web-console/src/views/datasource-view/datasource-view.tsx b/web-console/src/views/datasource-view/datasource-view.tsx
index b8aa885..22bd689 100644
--- a/web-console/src/views/datasource-view/datasource-view.tsx
+++ b/web-console/src/views/datasource-view/datasource-view.tsx
@@ -1016,7 +1016,6 @@ GROUP BY 1`;
datasourceId={datasourceTableActionDialogId}
actions={actions}
onClose={() => this.setState({ datasourceTableActionDialogId: undefined })}
- isOpen
/>
)}
</div>
diff --git a/web-console/src/views/home-view/status-card/status-card.tsx b/web-console/src/views/home-view/status-card/status-card.tsx
index a55247d..7b8f907 100644
--- a/web-console/src/views/home-view/status-card/status-card.tsx
+++ b/web-console/src/views/home-view/status-card/status-card.tsx
@@ -21,41 +21,50 @@ import axios from 'axios';
import React from 'react';
import { StatusDialog } from '../../../dialogs/status-dialog/status-dialog';
-import { QueryManager } from '../../../utils';
+import { pluralIfNeeded, QueryManager } from '../../../utils';
import { HomeViewCard } from '../home-view-card/home-view-card';
export interface StatusCardProps {}
export interface StatusCardState {
- versionLoading: boolean;
- version: string;
- versionError?: string;
+ statusLoading: boolean;
+ version?: string;
+ extensionCount?: number;
+ statusError?: string;
showStatusDialog: boolean;
}
+interface StatusSummary {
+ version: string;
+ extensionCount: number;
+}
+
export class StatusCard extends React.PureComponent<StatusCardProps, StatusCardState> {
- private versionQueryManager: QueryManager<null, string>;
+ private versionQueryManager: QueryManager<null, StatusSummary>;
constructor(props: StatusCardProps, context: any) {
super(props, context);
- this.state = {
- versionLoading: true,
- version: '',
+ this.state = {
+ statusLoading: true,
showStatusDialog: false,
};
this.versionQueryManager = new QueryManager({
processQuery: async () => {
const statusResp = await axios.get('/status');
- return statusResp.data.version;
+ return {
+ version: statusResp.data.version,
+ extensionCount: statusResp.data.modules.length,
+ };
},
onStateChange: ({ result, loading, error }) => {
this.setState({
- versionLoading: loading,
- version: result,
- versionError: error,
+ statusLoading: loading,
+ version: result ? result.version : undefined,
+ extensionCount: result ? result.extensionCount : undefined,
+ statusError: error,
});
},
});
@@ -69,35 +78,39 @@ export class StatusCard extends React.PureComponent<StatusCardProps, StatusCardS
this.versionQueryManager.terminate();
}
+ private handleStatusDialogOpen = () => {
+ this.setState({ showStatusDialog: true });
+ };
+
+ private handleStatusDialogClose = () => {
+ this.setState({ showStatusDialog: false });
+ };
+
renderStatusDialog() {
const { showStatusDialog } = this.state;
- if (!showStatusDialog) {
- return null;
- }
- return (
- <StatusDialog
- onClose={() => this.setState({ showStatusDialog: false })}
- title={'Status'}
- isOpen
- />
- );
+ if (!showStatusDialog) return;
+
+ return <StatusDialog onClose={this.handleStatusDialogClose} />;
}
render(): JSX.Element {
- const { version, versionLoading, versionError } = this.state;
+ const { version, extensionCount, statusLoading, statusError } = this.state;
return (
- <HomeViewCard
- className="status-card"
- onClick={() => this.setState({ showStatusDialog: true })}
- icon={IconNames.GRAPH}
- title="Status"
- loading={versionLoading}
- error={versionError}
- >
- {version ? `Apache Druid is running version ${version}` : ''}
+ <>
+ <HomeViewCard
+ className="status-card"
+ onClick={this.handleStatusDialogOpen}
+ icon={IconNames.GRAPH}
+ title="Status"
+ loading={statusLoading}
+ error={statusError}
+ >
+ {version && <p>{`Apache Druid is running version ${version}`}</p>}
+ {extensionCount && <p>{`${pluralIfNeeded(extensionCount, 'extension')} loaded`}</p>}
+ </HomeViewCard>
{this.renderStatusDialog()}
- </HomeViewCard>
+ </>
);
}
}
diff --git a/web-console/src/views/load-data-view/example-picker/__snapshots__/example-picker.spec.tsx.snap b/web-console/src/views/load-data-view/example-picker/__snapshots__/example-picker.spec.tsx.snap
new file mode 100644
index 0000000..47010e0f
--- /dev/null
+++ b/web-console/src/views/load-data-view/example-picker/__snapshots__/example-picker.spec.tsx.snap
@@ -0,0 +1,56 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`example picker matches snapshot 1`] = `
+<div
+ class="bp3-form-group"
+>
+ <label
+ class="bp3-label"
+ >
+ Select example dataset
+
+ <span
+ class="bp3-text-muted"
+ />
+ </label>
+ <div
+ class="bp3-form-content"
+ >
+ <div
+ class="bp3-html-select bp3-fill"
+ >
+ <select>
+ <option
+ value="0"
+ >
+ Wikipedia
+ </option>
+ <option
+ value="1"
+ >
+ Ex 2
+ </option>
+ </select>
+ <span
+ class="bp3-icon bp3-icon-double-caret-vertical"
+ icon="double-caret-vertical"
+ >
+ <svg
+ data-icon="double-caret-vertical"
+ height="16"
+ viewBox="0 0 16 16"
+ width="16"
+ >
+ <desc>
+ double-caret-vertical
+ </desc>
+ <path
+ d="M5 7h6a1.003 1.003 0 00.71-1.71l-3-3C8.53 2.11 8.28 2 8 2s-.53.11-.71.29l-3 3A1.003 1.003 0 005 7zm6 2H5a1.003 1.003 0 00-.71 1.71l3 3c.18.18.43.29.71.29s.53-.11.71-.29l3-3A1.003 1.003 0 0011 9z"
+ fill-rule="evenodd"
+ />
+ </svg>
+ </span>
+ </div>
+ </div>
+</div>
+`;
diff --git a/web-console/src/dialogs/history-dialog/history-dialog.spec.tsx b/web-console/src/views/load-data-view/example-picker/example-picker.spec.tsx
similarity index 68%
copy from web-console/src/dialogs/history-dialog/history-dialog.spec.tsx
copy to web-console/src/views/load-data-view/example-picker/example-picker.spec.tsx
index 176f35a..e5b070f 100644
--- a/web-console/src/dialogs/history-dialog/history-dialog.spec.tsx
+++ b/web-console/src/views/load-data-view/example-picker/example-picker.spec.tsx
@@ -19,20 +19,21 @@
import { render } from '@testing-library/react';
import React from 'react';
-import { HistoryDialog } from './history-dialog';
+import { ExamplePicker } from './example-picker';
-describe('history dialog', () => {
+describe('example picker', () => {
it('matches snapshot', () => {
- const historyDialog = (
- <HistoryDialog
- historyRecords={[
- { auditTime: 'test', auditInfo: 'test', payload: JSON.stringify({ name: 'test' }) },
- { auditTime: 'test', auditInfo: 'test', payload: JSON.stringify({ name: 'test' }) },
+ const examplePicker = (
+ <ExamplePicker
+ exampleManifests={[
+ { name: 'Wikipedia', description: 'stuff stuff', spec: {} },
+ { name: 'Ex 2', description: 'stuff stuff', spec: {} },
]}
- isOpen
+ onSelectExample={() => {}}
/>
);
- render(historyDialog);
- expect(document.body.lastChild).toMatchSnapshot();
+
+ const { container } = render(examplePicker);
+ expect(container.firstChild).toMatchSnapshot();
});
});
diff --git a/web-console/src/views/load-data-view/example-picker/example-picker.tsx b/web-console/src/views/load-data-view/example-picker/example-picker.tsx
new file mode 100644
index 0000000..39596ff
--- /dev/null
+++ b/web-console/src/views/load-data-view/example-picker/example-picker.tsx
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Button, Callout, FormGroup, HTMLSelect, Intent } from '@blueprintjs/core';
+import { IconNames } from '@blueprintjs/icons';
+import React from 'react';
+
+import { ExampleManifest } from '../../../utils/sampler';
+
+export interface ExamplePickerProps {
+ exampleManifests: ExampleManifest[];
+ onSelectExample: (exampleManifest: ExampleManifest) => void;
+}
+
+export interface ExamplePickerState {
+ selectedIndex: number;
+}
+
+export class ExamplePicker extends React.PureComponent<ExamplePickerProps, ExamplePickerState> {
+ constructor(props: ExamplePickerProps, context: any) {
+ super(props, context);
+ this.state = {
+ selectedIndex: 0,
+ };
+ }
+
+ render(): JSX.Element {
+ const { exampleManifests, onSelectExample } = this.props;
+ const { selectedIndex } = this.state;
+
+ return (
+ <>
+ <FormGroup label="Select example dataset">
+ <HTMLSelect
+ fill
+ value={selectedIndex}
+ onChange={e => this.setState({ selectedIndex: e.target.value as any })}
+ >
+ {exampleManifests.map((exampleManifest, i) => (
+ <option key={i} value={i}>
+ {exampleManifest.name}
+ </option>
+ ))}
+ </HTMLSelect>
+ </FormGroup>
+ <FormGroup>
+ <Callout>{exampleManifests[selectedIndex].description}</Callout>
+ </FormGroup>
+ <FormGroup>
+ <Button
+ text="Load example"
+ rightIcon={IconNames.ARROW_RIGHT}
+ intent={Intent.PRIMARY}
+ onClick={() => {
+ onSelectExample(exampleManifests[selectedIndex]);
+ }}
+ />
+ </FormGroup>
+ </>
+ );
+ }
+}
diff --git a/web-console/src/views/load-data-view/load-data-view.tsx b/web-console/src/views/load-data-view/load-data-view.tsx
index 71d71c8..00eb64b 100644
--- a/web-console/src/views/load-data-view/load-data-view.tsx
+++ b/web-console/src/views/load-data-view/load-data-view.tsx
@@ -29,6 +29,7 @@ import {
H5,
HTMLSelect,
Icon,
+ IconName,
Intent,
Popover,
Switch,
@@ -71,7 +72,7 @@ import {
DruidFilter,
EMPTY_ARRAY,
EMPTY_OBJECT,
- fillDataSourceName,
+ fillDataSourceNameIfNeeded,
fillParser,
FlattenField,
getDimensionMode,
@@ -101,6 +102,7 @@ import {
IoConfig,
isColumnTimestampSpec,
isEmptyIngestionSpec,
+ isIngestSegment,
isParallel,
issueWithIoConfig,
issueWithParser,
@@ -132,10 +134,12 @@ import {
sampleForTimestamp,
sampleForTransform,
SampleResponse,
+ SampleResponseWithExtraInfo,
SampleStrategy,
} from '../../utils/sampler';
import { computeFlattenPathsForData } from '../../utils/spec-utils';
+import { ExamplePicker } from './example-picker/example-picker';
import { FilterTable } from './filter-table/filter-table';
import { ParseDataTable } from './parse-data-table/parse-data-table';
import { ParseTimeTable } from './parse-time-table/parse-time-table';
@@ -261,7 +265,7 @@ export interface LoadDataViewState {
specialColumnsOnly: boolean;
// for ioConfig
- inputQueryState: QueryState<SampleEntry[]>;
+ inputQueryState: QueryState<SampleResponseWithExtraInfo>;
// for parser
parserQueryState: QueryState<HeaderAndRows>;
@@ -449,32 +453,36 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
localStorageSet(LocalStorageKeys.INGESTION_SPEC, JSON.stringify(newSpec));
};
+ renderActionCard(icon: IconName, title: string, caption: string, onClick: () => void) {
+ return (
+ <Card className={'spec-card'} interactive onClick={onClick}>
+ <Icon className="spec-card-icon" icon={icon} iconSize={30} />
+ <div className={'spec-card-header'}>
+ {title}
+ <div className={'spec-card-caption'}>{caption}</div>
+ </div>
+ </Card>
+ );
+ }
+
render(): JSX.Element {
const { step, continueToSpec } = this.state;
+
if (!continueToSpec) {
return (
<div className={classNames('load-data-continue-view load-data-view')}>
- <Card className={'spec-card'} interactive onClick={this.handleResetSpec}>
- <Icon className="spec-card-icon" icon={IconNames.ASTERISK} iconSize={30} />
- <div className={'spec-card-header'}>
- Start a new spec
- <div className={'spec-card-caption'}>Start a new ingestion flow</div>
- </div>
- </Card>
- <Card
- className={'spec-card'}
- interactive
- onClick={() => this.setState({ continueToSpec: true })}
- >
- <Icon className="spec-card-icon" icon={IconNames.REPEAT} iconSize={30} />
- <div className={'spec-card-header'}>
- Continue from previous spec
- <div className={'spec-card-caption'}>
- Continue from most recent spec you were working on
- </div>
- </div>
- </Card>
- {this.renderResetConfirm()}
+ {this.renderActionCard(
+ IconNames.ASTERISK,
+ 'Start a new spec',
+ 'Begin a new ingestion flow',
+ this.handleResetSpec,
+ )}
+ {this.renderActionCard(
+ IconNames.REPEAT,
+ 'Continue from previous spec',
+ 'Go back to the most recent spec you were working on',
+ this.handleContinueSpec,
+ )}
</div>
);
}
@@ -586,9 +594,12 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
});
}
- renderIngestionCard(comboType: IngestionComboTypeWithExtra, disabled?: boolean) {
+ renderIngestionCard(
+ comboType: IngestionComboTypeWithExtra,
+ disabled?: boolean,
+ ): JSX.Element | undefined {
const { overlordModules, selectedComboType } = this.state;
- if (!overlordModules) return null;
+ if (!overlordModules) return;
const requiredModule = getRequiredModule(comboType);
const goodToGo = !disabled && (!requiredModule || overlordModules.includes(requiredModule));
@@ -613,6 +624,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
const { spec, exampleManifests } = this.state;
const noExamples = Boolean(!exampleManifests || !exampleManifests.length);
+ const welcomeMessage = this.renderWelcomeStepMessage();
return (
<>
<div className="main bp3-input">
@@ -629,7 +641,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
{this.renderIngestionCard('other')}
</div>
<div className="control">
- <Callout className="intro">{this.renderWelcomeStepMessage()}</Callout>
+ {welcomeMessage && <Callout className="intro">{welcomeMessage}</Callout>}
{this.renderWelcomeStepControls()}
{!isEmptyIngestionSpec(spec) && (
<Button icon={IconNames.RESET} text="Reset spec" onClick={this.handleResetConfirm} />
@@ -639,15 +651,16 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
);
}
- renderViewValueModal() {
+ renderViewValueModal(): JSX.Element | undefined {
const { showViewValueModal, str } = this.state;
- if (!showViewValueModal) return null;
+ if (!showViewValueModal) return;
+
return (
<ShowValueDialog onClose={() => this.setState({ showViewValueModal: false })} str={str} />
);
}
- renderWelcomeStepMessage() {
+ renderWelcomeStepMessage(): JSX.Element | undefined {
const { selectedComboType, exampleManifests } = this.state;
if (!selectedComboType) {
@@ -735,9 +748,9 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
case 'example':
if (exampleManifests && exampleManifests.length) {
- return <p>Pick one of these examples to get you started.</p>;
+ return; // Yield to example picker controls
} else {
- return <p>Could not load example.</p>;
+ return <p>Could not load examples.</p>;
}
case 'other':
@@ -756,12 +769,12 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
}
}
- renderWelcomeStepControls() {
+ renderWelcomeStepControls(): JSX.Element | undefined {
const { goToTask } = this.props;
const { spec, selectedComboType, exampleManifests } = this.state;
const issue = this.selectedIngestionTypeIssue();
- if (issue) return null;
+ if (issue) return;
switch (selectedComboType) {
case 'index:http':
@@ -801,27 +814,17 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
);
case 'example':
+ if (!exampleManifests) return;
return (
- <>
- {exampleManifests &&
- exampleManifests.map((exampleManifest, i) => (
- <FormGroup key={i}>
- <Button
- text={exampleManifest.name}
- rightIcon={IconNames.ARROW_RIGHT}
- intent={Intent.PRIMARY}
- title={exampleManifest.description}
- fill
- onClick={() => {
- this.updateSpec(exampleManifest.spec);
- setTimeout(() => {
- this.updateStep('connect');
- }, 10);
- }}
- />
- </FormGroup>
- ))}
- </>
+ <ExamplePicker
+ exampleManifests={exampleManifests}
+ onSelectExample={exampleManifest => {
+ this.updateSpec(exampleManifest.spec);
+ setTimeout(() => {
+ this.updateStep('connect');
+ }, 10);
+ }}
+ />
);
case 'other':
@@ -847,23 +850,36 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
);
default:
- return null;
+ return;
}
}
- selectedIngestionTypeIssue(): JSX.Element | null {
+ selectedIngestionTypeIssue(): JSX.Element | undefined {
const { selectedComboType, overlordModules } = this.state;
- if (!selectedComboType || !overlordModules) return null;
+ if (!selectedComboType || !overlordModules) return;
const requiredModule = getRequiredModule(selectedComboType);
- if (!requiredModule || overlordModules.includes(requiredModule)) return null;
+ if (!requiredModule || overlordModules.includes(requiredModule)) return;
return (
- <p>
- {`${getIngestionTitle(selectedComboType)} ingestion requires the `}
- <strong>{requiredModule}</strong>
- {` extension to be loaded.`}
- </p>
+ <>
+ <p>
+ {`${getIngestionTitle(selectedComboType)} ingestion requires the `}
+ <strong>{requiredModule}</strong>
+ {` extension to be loaded.`}
+ </p>
+ <p>
+ Please make sure that the
+ <Code>"{requiredModule}"</Code> extension is included in the <Code>loadList</Code>.
+ </p>
+ <p>
+ For more information please refer to the{' '}
+ <ExternalLink href="https://druid.apache.org/docs/latest/operations/including-extensions">
+ documentation on loading extensions
+ </ExternalLink>
+ .
+ </p>
+ </>
);
}
@@ -872,13 +888,18 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
};
private handleResetSpec = () => {
- this.setState({ showResetConfirm: false, step: 'welcome', continueToSpec: true });
+ this.setState({ showResetConfirm: false, continueToSpec: true });
this.updateSpec({} as any);
+ this.updateStep('welcome');
+ };
+
+ private handleContinueSpec = () => {
+ this.setState({ continueToSpec: true });
};
- renderResetConfirm() {
+ renderResetConfirm(): JSX.Element | undefined {
const { showResetConfirm } = this.state;
- if (!showResetConfirm) return null;
+ if (!showResetConfirm) return;
return (
<Alert
@@ -929,7 +950,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
this.setState({
cacheKey: sampleResponse.cacheKey,
- inputQueryState: new QueryState({ data: sampleResponse.data }),
+ inputQueryState: new QueryState({ data: sampleResponse }),
});
}
@@ -965,7 +986,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
} else if (inputQueryState.error) {
mainFill = <CenterMessage>{`Error: ${inputQueryState.error}`}</CenterMessage>;
} else if (inputQueryState.data) {
- const inputData = inputQueryState.data;
+ const inputData = inputQueryState.data.data;
mainFill = (
<TextArea
className="raw-lines"
@@ -1054,9 +1075,61 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
disabled: !inputQueryState.data,
onNextStep: () => {
if (!inputQueryState.data) return;
- this.updateSpec(
- fillDataSourceName(fillParser(spec, inputQueryState.data.map(l => l.raw))),
- );
+ const inputData = inputQueryState.data;
+
+ if (isIngestSegment(spec)) {
+ let newSpec = fillParser(spec, []);
+
+ if (typeof inputData.rollup === 'boolean') {
+ newSpec = deepSet(newSpec, 'dataSchema.granularitySpec.rollup', inputData.rollup);
+ }
+
+ if (inputData.timestampSpec) {
+ newSpec = deepSet(
+ newSpec,
+ 'dataSchema.parser.parseSpec.timestampSpec',
+ inputData.timestampSpec,
+ );
+ }
+
+ if (inputData.queryGranularity) {
+ newSpec = deepSet(
+ newSpec,
+ 'dataSchema.granularitySpec.queryGranularity',
+ inputData.queryGranularity,
+ );
+ }
+
+ if (inputData.columns) {
+ const aggregators = inputData.aggregators || {};
+ newSpec = deepSet(
+ newSpec,
+ 'dataSchema.parser.parseSpec.dimensionsSpec.dimensions',
+ Object.keys(inputData.columns)
+ .filter(k => k !== '__time' && !aggregators[k])
+ .map(k => ({
+ name: k,
+ type: String(inputData.columns![k].type || 'string').toLowerCase(),
+ })),
+ );
+ }
+
+ if (inputData.aggregators) {
+ newSpec = deepSet(
+ newSpec,
+ 'dataSchema.metricsSpec',
+ Object.values(inputData.aggregators),
+ );
+ }
+
+ this.updateSpec(fillDataSourceNameIfNeeded(newSpec));
+ } else {
+ this.updateSpec(
+ fillDataSourceNameIfNeeded(
+ fillParser(spec, inputQueryState.data.data.map(l => l.raw)),
+ ),
+ );
+ }
},
})}
</>
@@ -1231,7 +1304,16 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
disabled: !parserQueryState.data,
onNextStep: () => {
if (!parserQueryState.data) return;
- const possibleTimestampSpec = getTimestampSpec(parserQueryState.data);
+ let possibleTimestampSpec: TimestampSpec;
+ if (isIngestSegment(spec)) {
+ possibleTimestampSpec = {
+ column: '__time',
+ format: 'auto',
+ };
+ } else {
+ possibleTimestampSpec = getTimestampSpec(parserQueryState.data);
+ }
+
if (possibleTimestampSpec) {
const newSpec: IngestionSpec = deepSet(
spec,
@@ -1253,10 +1335,10 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
});
};
- renderFlattenControls() {
+ renderFlattenControls(): JSX.Element | undefined {
const { spec, selectedFlattenField, selectedFlattenFieldIndex } = this.state;
const parseSpec: ParseSpec = deepGet(spec, 'dataSchema.parser.parseSpec') || EMPTY_OBJECT;
- if (!parseSpecHasFlatten(parseSpec)) return null;
+ if (!parseSpecHasFlatten(parseSpec)) return;
const close = () => {
this.setState({
@@ -1640,9 +1722,11 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
disabled: !transformQueryState.data,
onNextStep: () => {
if (!transformQueryState.data) return;
- this.updateSpec(
- updateSchemaWithSample(spec, transformQueryState.data, 'specific', true),
- );
+ if (!isIngestSegment(spec)) {
+ this.updateSpec(
+ updateSchemaWithSample(spec, transformQueryState.data, 'specific', true),
+ );
+ }
},
})}
</>
@@ -2640,9 +2724,9 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
);
}
- renderParallelPickerIfNeeded() {
+ renderParallelPickerIfNeeded(): JSX.Element | undefined {
const { spec } = this.state;
- if (!hasParallelAbility(spec)) return null;
+ if (!hasParallelAbility(spec)) return;
return (
<FormGroup>
diff --git a/web-console/src/views/lookups-view/__snapshots__/lookups-view.spec.tsx.snap b/web-console/src/views/lookups-view/__snapshots__/lookups-view.spec.tsx.snap
index 58c894d..87d09b8 100755
--- a/web-console/src/views/lookups-view/__snapshots__/lookups-view.spec.tsx.snap
+++ b/web-console/src/views/lookups-view/__snapshots__/lookups-view.spec.tsx.snap
@@ -208,17 +208,5 @@ exports[`lookups view matches snapshot 1`] = `
style={Object {}}
subRowsKey="_subRows"
/>
- <LookupEditDialog
- allLookupTiers={Array []}
- isEdit={false}
- isOpen={false}
- lookupName=""
- lookupSpec=""
- lookupTier=""
- lookupVersion=""
- onChange={[Function]}
- onClose={[Function]}
- onSubmit={[Function]}
- />
</div>
`;
diff --git a/web-console/src/views/lookups-view/lookups-view.tsx b/web-console/src/views/lookups-view/lookups-view.tsx
index bf22f5f..52bdf18 100644
--- a/web-console/src/views/lookups-view/lookups-view.tsx
+++ b/web-console/src/views/lookups-view/lookups-view.tsx
@@ -180,26 +180,26 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
isEdit,
} = this.state;
let endpoint = '/druid/coordinator/v1/lookups/config';
- const specJSON: any = JSON.parse(lookupEditSpec);
- let dataJSON: any;
+ const specJson: any = JSON.parse(lookupEditSpec);
+ let dataJson: any;
if (isEdit) {
endpoint = `${endpoint}/${lookupEditTier}/${lookupEditName}`;
- dataJSON = {
+ dataJson = {
version: lookupEditVersion,
- lookupExtractorFactory: specJSON,
+ lookupExtractorFactory: specJson,
};
} else {
- dataJSON = {
+ dataJson = {
[lookupEditTier]: {
[lookupEditName]: {
version: lookupEditVersion,
- lookupExtractorFactory: specJSON,
+ lookupExtractorFactory: specJson,
},
},
};
}
try {
- await axios.post(endpoint, dataJSON);
+ await axios.post(endpoint, dataJson);
this.setState({
lookupEditDialogOpen: false,
});
@@ -346,10 +346,10 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
lookupEditVersion,
isEdit,
} = this.state;
+ if (!lookupEditDialogOpen) return;
return (
<LookupEditDialog
- isOpen={lookupEditDialogOpen}
onClose={() => this.setState({ lookupEditDialogOpen: false })}
onSubmit={() => this.submitLookupEdit()}
onChange={this.handleChangeLookup}
diff --git a/web-console/src/views/segments-view/segments-view.tsx b/web-console/src/views/segments-view/segments-view.tsx
index cf6615d..adb36f8 100644
--- a/web-console/src/views/segments-view/segments-view.tsx
+++ b/web-console/src/views/segments-view/segments-view.tsx
@@ -711,7 +711,6 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
datasourceId={datasourceTableActionDialogId}
actions={actions}
onClose={() => this.setState({ segmentTableActionDialogId: undefined })}
- isOpen
/>
)}
</>
diff --git a/web-console/src/views/task-view/tasks-view.tsx b/web-console/src/views/task-view/tasks-view.tsx
index 3b635ce..3316934 100644
--- a/web-console/src/views/task-view/tasks-view.tsx
+++ b/web-console/src/views/task-view/tasks-view.tsx
@@ -1204,7 +1204,6 @@ ORDER BY "rank" DESC, "created_time" DESC`;
</Alert>
{supervisorTableActionDialogId && (
<SupervisorTableActionDialog
- isOpen
supervisorId={supervisorTableActionDialogId}
actions={supervisorTableActionDialogActions}
onClose={() => this.setState({ supervisorTableActionDialogId: undefined })}
@@ -1212,7 +1211,6 @@ ORDER BY "rank" DESC, "created_time" DESC`;
)}
{taskTableActionDialogId && taskTableActionDialogStatus && (
<TaskTableActionDialog
- isOpen
status={taskTableActionDialogStatus}
taskId={taskTableActionDialogId}
actions={taskTableActionDialogActions}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org