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 <cwy...@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-unsubscr...@druid.apache.org For additional commands, e-mail: commits-h...@druid.apache.org