mirror of https://github.com/apache/druid.git
Web-Console: add go to editor button to tasks and supervisors view (#7705)
* add go to editor button to tasks and supervisors * fix package.json * remove intent * quuick fixes * fixes * add getsupervisorjson and gettaskjson * remove space * remove gotoloaddata * fixes * add error handling * save * add loader * remove initspec * fixup! add loader * update snapshots * remove gotoloaddataview form headerbar.spec
This commit is contained in:
parent
2b34e1b710
commit
5f50f357a4
|
@ -27,7 +27,6 @@ describe('describe header bar', () => {
|
|||
<HeaderBar
|
||||
active={'load-data'}
|
||||
hideLegacy={false}
|
||||
goToLoadDataView={() => {}}
|
||||
/>);
|
||||
expect(headerBar).toMatchSnapshot();
|
||||
});
|
||||
|
|
|
@ -52,7 +52,6 @@ export type HeaderActiveTab = null | 'load-data' | 'query' | 'datasources' | 'se
|
|||
export interface HeaderBarProps extends React.Props<any> {
|
||||
active: HeaderActiveTab;
|
||||
hideLegacy: boolean;
|
||||
goToLoadDataView: () => void;
|
||||
}
|
||||
|
||||
export interface HeaderBarState {
|
||||
|
|
|
@ -100,7 +100,7 @@ export class ConsoleApplication extends React.Component<ConsoleApplicationProps,
|
|||
});
|
||||
}
|
||||
|
||||
private initSpec: any | null;
|
||||
private supervisorId: string | null;
|
||||
private taskId: string | null;
|
||||
private openDialog: string | null;
|
||||
private datasource: string | null;
|
||||
|
@ -151,8 +151,8 @@ export class ConsoleApplication extends React.Component<ConsoleApplicationProps,
|
|||
|
||||
private resetInitialsWithDelay() {
|
||||
setTimeout(() => {
|
||||
this.initSpec = null;
|
||||
this.taskId = null;
|
||||
this.supervisorId = null;
|
||||
this.openDialog = null;
|
||||
this.datasource = null;
|
||||
this.onlyUnavailable = null;
|
||||
|
@ -161,8 +161,9 @@ export class ConsoleApplication extends React.Component<ConsoleApplicationProps,
|
|||
}, 50);
|
||||
}
|
||||
|
||||
private goToLoadDataView = (initSpec?: any) => {
|
||||
if (initSpec) this.initSpec = initSpec;
|
||||
private goToLoadDataView = (supervisorId?: string, taskId?: string ) => {
|
||||
if (taskId) this.taskId = taskId;
|
||||
if (supervisorId) this.supervisorId = supervisorId;
|
||||
window.location.hash = 'load-data';
|
||||
this.resetInitialsWithDelay();
|
||||
}
|
||||
|
@ -197,7 +198,7 @@ export class ConsoleApplication extends React.Component<ConsoleApplicationProps,
|
|||
const { hideLegacy } = this.props;
|
||||
|
||||
return <>
|
||||
<HeaderBar active={active} hideLegacy={hideLegacy} goToLoadDataView={this.goToLoadDataView}/>
|
||||
<HeaderBar active={active} hideLegacy={hideLegacy}/>
|
||||
<div className={classNames('view-container', classType)}>{el}</div>
|
||||
</>;
|
||||
}
|
||||
|
@ -208,7 +209,8 @@ export class ConsoleApplication extends React.Component<ConsoleApplicationProps,
|
|||
}
|
||||
|
||||
private wrappedLoadDataView = () => {
|
||||
return this.wrapInViewContainer('load-data', <LoadDataView initSpec={this.initSpec} goToTask={this.goToTask}/>, 'narrow-pad');
|
||||
|
||||
return this.wrapInViewContainer('load-data', <LoadDataView initSupervisorId={this.supervisorId} initTaskId={this.taskId} goToTask={this.goToTask}/>, 'narrow-pad');
|
||||
}
|
||||
|
||||
private wrappedSqlView = () => {
|
||||
|
|
|
@ -2,211 +2,41 @@
|
|||
|
||||
exports[`describe load data view load data view snapshot 1`] = `
|
||||
<div
|
||||
className="load-data-view app-view connect"
|
||||
className="load-data-view app-view init"
|
||||
>
|
||||
<div
|
||||
className="bp3-tabs stage-nav"
|
||||
className="intro"
|
||||
>
|
||||
<div
|
||||
className="stage-section"
|
||||
key="Connect and parse raw data"
|
||||
>
|
||||
<div
|
||||
className="stage-nav-l1"
|
||||
>
|
||||
Connect and parse raw data
|
||||
</div>
|
||||
<Blueprint3.ButtonGroup
|
||||
className="stage-nav-l2"
|
||||
>
|
||||
<Blueprint3.Button
|
||||
active={true}
|
||||
className="connect"
|
||||
icon={false}
|
||||
key="connect"
|
||||
onClick={[Function]}
|
||||
text="Connect"
|
||||
/>
|
||||
<Blueprint3.Button
|
||||
active={false}
|
||||
className="parser"
|
||||
icon={false}
|
||||
key="parser"
|
||||
onClick={[Function]}
|
||||
text="Parse data"
|
||||
/>
|
||||
<Blueprint3.Button
|
||||
active={false}
|
||||
className="timestamp"
|
||||
icon={false}
|
||||
key="timestamp"
|
||||
onClick={[Function]}
|
||||
text="Parse time"
|
||||
/>
|
||||
</Blueprint3.ButtonGroup>
|
||||
</div>
|
||||
<div
|
||||
className="stage-section"
|
||||
key="Transform and configure schema"
|
||||
>
|
||||
<div
|
||||
className="stage-nav-l1"
|
||||
>
|
||||
Transform and configure schema
|
||||
</div>
|
||||
<Blueprint3.ButtonGroup
|
||||
className="stage-nav-l2"
|
||||
>
|
||||
<Blueprint3.Button
|
||||
active={false}
|
||||
className="transform"
|
||||
icon={false}
|
||||
key="transform"
|
||||
onClick={[Function]}
|
||||
text="Transform"
|
||||
/>
|
||||
<Blueprint3.Button
|
||||
active={false}
|
||||
className="filter"
|
||||
icon={false}
|
||||
key="filter"
|
||||
onClick={[Function]}
|
||||
text="Filter"
|
||||
/>
|
||||
<Blueprint3.Button
|
||||
active={false}
|
||||
className="schema"
|
||||
icon={false}
|
||||
key="schema"
|
||||
onClick={[Function]}
|
||||
text="Configure schema"
|
||||
/>
|
||||
</Blueprint3.ButtonGroup>
|
||||
</div>
|
||||
<div
|
||||
className="stage-section"
|
||||
key="Tune parameters"
|
||||
>
|
||||
<div
|
||||
className="stage-nav-l1"
|
||||
>
|
||||
Tune parameters
|
||||
</div>
|
||||
<Blueprint3.ButtonGroup
|
||||
className="stage-nav-l2"
|
||||
>
|
||||
<Blueprint3.Button
|
||||
active={false}
|
||||
className="partition"
|
||||
icon={false}
|
||||
key="partition"
|
||||
onClick={[Function]}
|
||||
text="Partition"
|
||||
/>
|
||||
<Blueprint3.Button
|
||||
active={false}
|
||||
className="tuning"
|
||||
icon={false}
|
||||
key="tuning"
|
||||
onClick={[Function]}
|
||||
text="Tune"
|
||||
/>
|
||||
<Blueprint3.Button
|
||||
active={false}
|
||||
className="publish"
|
||||
icon={false}
|
||||
key="publish"
|
||||
onClick={[Function]}
|
||||
text="Publish"
|
||||
/>
|
||||
</Blueprint3.ButtonGroup>
|
||||
</div>
|
||||
<div
|
||||
className="stage-section"
|
||||
key="Verify and submit"
|
||||
>
|
||||
<div
|
||||
className="stage-nav-l1"
|
||||
>
|
||||
Verify and submit
|
||||
</div>
|
||||
<Blueprint3.ButtonGroup
|
||||
className="stage-nav-l2"
|
||||
>
|
||||
<Blueprint3.Button
|
||||
active={false}
|
||||
className="json-spec"
|
||||
icon="eye-open"
|
||||
key="json-spec"
|
||||
onClick={[Function]}
|
||||
text="Edit JSON spec"
|
||||
/>
|
||||
</Blueprint3.ButtonGroup>
|
||||
</div>
|
||||
Please specify where your raw data is located
|
||||
</div>
|
||||
<div
|
||||
className="main"
|
||||
className="cards"
|
||||
>
|
||||
<Loader
|
||||
loading={true}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="control"
|
||||
>
|
||||
<Blueprint3.Callout
|
||||
className="intro"
|
||||
<Blueprint3.Card
|
||||
elevation={0}
|
||||
interactive={true}
|
||||
onClick={[Function]}
|
||||
>
|
||||
<p>
|
||||
Druid ingests raw data and converts it into a custom,
|
||||
<ExternalLink
|
||||
href="http://druid.io/docs/latest/design/segments.html"
|
||||
>
|
||||
indexed
|
||||
</ExternalLink>
|
||||
format that is optimized for analytic queries.
|
||||
</p>
|
||||
<p>
|
||||
To get started, please specify where your raw data is stored and what data you want to ingest.
|
||||
</p>
|
||||
<p>
|
||||
Click "Preview" to look at the sampled raw data.
|
||||
</p>
|
||||
</Blueprint3.Callout>
|
||||
<Blueprint3.FormGroup
|
||||
label="IO Config"
|
||||
Other (streaming)
|
||||
</Blueprint3.Card>
|
||||
<Blueprint3.Card
|
||||
elevation={0}
|
||||
interactive={true}
|
||||
onClick={[Function]}
|
||||
>
|
||||
<JSONInput
|
||||
height="300px"
|
||||
onChange={[Function]}
|
||||
value={
|
||||
Object {
|
||||
"type": "test",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Blueprint3.FormGroup>
|
||||
<Blueprint3.Button
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
text="Preview"
|
||||
/>
|
||||
Other (batch)
|
||||
</Blueprint3.Card>
|
||||
</div>
|
||||
<div
|
||||
className="next-bar"
|
||||
<Blueprint3.Alert
|
||||
canEscapeKeyCancel={false}
|
||||
canOutsideClickCancel={false}
|
||||
confirmButtonText="Close"
|
||||
icon="warning-sign"
|
||||
intent="warning"
|
||||
isOpen={false}
|
||||
onConfirm={[Function]}
|
||||
>
|
||||
<Blueprint3.Button
|
||||
className="prev"
|
||||
icon="arrow-left"
|
||||
onClick={[Function]}
|
||||
text="Restart"
|
||||
/>
|
||||
<Blueprint3.Button
|
||||
disabled={true}
|
||||
intent="primary"
|
||||
onClick={[Function]}
|
||||
text="Next: Parse data"
|
||||
/>
|
||||
</div>
|
||||
<p />
|
||||
</Blueprint3.Alert>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -29,7 +29,6 @@ describe('describe load data view', () => {
|
|||
it('load data view snapshot', () => {
|
||||
const loadDataView = shallow(
|
||||
<LoadDataView
|
||||
initSpec={{dataSchema: {dataSource: 'test', parser:{parseSpec: {format: 'test', dimensionsSpec: {}, timestampSpec: {column: 'test', format: 'test', missingValue: 'test'}, }}}, ioConfig: { type: 'test'}}}
|
||||
goToTask={(taskId: string | null) => {}}
|
||||
/>);
|
||||
expect(loadDataView).toMatchSnapshot();
|
||||
|
|
|
@ -143,8 +143,8 @@ function getTimestampSpec(headerAndRows: HeaderAndRows | null): TimestampSpec {
|
|||
return timestampSpecs[0] || getEmptyTimestampSpec();
|
||||
}
|
||||
|
||||
type Stage = 'connect' | 'parser' | 'timestamp' | 'transform' | 'filter' | 'schema' | 'partition' | 'tuning' | 'publish' | 'json-spec';
|
||||
const STAGES: Stage[] = ['connect', 'parser', 'timestamp', 'transform', 'filter', 'schema', 'partition', 'tuning', 'publish', 'json-spec'];
|
||||
type Stage = 'connect' | 'parser' | 'timestamp' | 'transform' | 'filter' | 'schema' | 'partition' | 'tuning' | 'publish' | 'json-spec' | 'loading';
|
||||
const STAGES: Stage[] = ['connect', 'parser', 'timestamp', 'transform', 'filter', 'schema', 'partition', 'tuning', 'publish', 'json-spec', 'loading'];
|
||||
|
||||
const SECTIONS: { name: string, stages: Stage[] }[] = [
|
||||
{ name: 'Connect and parse raw data', stages: ['connect', 'parser', 'timestamp'] },
|
||||
|
@ -163,19 +163,20 @@ const VIEW_TITLE: Record<Stage, string> = {
|
|||
'partition': 'Partition',
|
||||
'tuning': 'Tune',
|
||||
'publish': 'Publish',
|
||||
'json-spec': 'Edit JSON spec'
|
||||
'json-spec': 'Edit JSON spec',
|
||||
'loading': 'Loading'
|
||||
};
|
||||
|
||||
export interface LoadDataViewProps extends React.Props<any> {
|
||||
initSpec: IngestionSpec | null;
|
||||
goToTask: (taskId: string | null, openDialog?: string) => void;
|
||||
initSupervisorId?: string | null;
|
||||
initTaskId?: string | null;
|
||||
goToTask: (taskId: string | null, supervisor?: string) => void;
|
||||
}
|
||||
|
||||
export interface LoadDataViewState {
|
||||
stage: Stage;
|
||||
spec: IngestionSpec;
|
||||
cacheKey: string | undefined;
|
||||
|
||||
// dialogs / modals
|
||||
showResetConfirm: boolean;
|
||||
newRollup: boolean | null;
|
||||
|
@ -225,9 +226,8 @@ export class LoadDataView extends React.Component<LoadDataViewProps, LoadDataVie
|
|||
constructor(props: LoadDataViewProps) {
|
||||
super(props);
|
||||
|
||||
let spec = props.initSpec || parseJson(String(localStorageGet(LocalStorageKeys.INGESTION_SPEC)));
|
||||
let spec = parseJson(String(localStorageGet(LocalStorageKeys.INGESTION_SPEC)));
|
||||
if (!spec || typeof spec !== 'object') spec = {};
|
||||
|
||||
this.state = {
|
||||
stage: 'connect',
|
||||
spec,
|
||||
|
@ -281,9 +281,15 @@ export class LoadDataView extends React.Component<LoadDataViewProps, LoadDataVie
|
|||
|
||||
componentDidMount(): void {
|
||||
this.getOverlordModules();
|
||||
this.updateStage('connect');
|
||||
if (this.props.initTaskId) {
|
||||
this.updateStage('loading');
|
||||
this.getTaskJson();
|
||||
} else if (this.props.initSupervisorId) {
|
||||
this.updateStage('loading');
|
||||
this.getSupervisorJson(); } else this.updateStage('connect');
|
||||
}
|
||||
|
||||
|
||||
async getOverlordModules() {
|
||||
let overlordModules: string[];
|
||||
try {
|
||||
|
@ -327,8 +333,7 @@ export class LoadDataView extends React.Component<LoadDataViewProps, LoadDataVie
|
|||
|
||||
render() {
|
||||
const { stage, spec } = this.state;
|
||||
|
||||
if (!Object.keys(spec).length) {
|
||||
if (!Object.keys(spec).length && !this.props.initSupervisorId && !this.props.initTaskId) {
|
||||
return <div className={classNames('load-data-view', 'app-view', 'init')}>
|
||||
{this.renderInitStage()}
|
||||
</div>;
|
||||
|
@ -350,11 +355,11 @@ export class LoadDataView extends React.Component<LoadDataViewProps, LoadDataVie
|
|||
{stage === 'publish' && this.renderPublishStage()}
|
||||
|
||||
{stage === 'json-spec' && this.renderJsonSpecStage()}
|
||||
{stage === 'loading' && this.renderLoading()}
|
||||
|
||||
{this.renderResetConfirm()}
|
||||
</div>;
|
||||
}
|
||||
|
||||
renderStepNav() {
|
||||
const { stage } = this.state;
|
||||
|
||||
|
@ -1052,7 +1057,7 @@ export class LoadDataView extends React.Component<LoadDataViewProps, LoadDataVie
|
|||
},
|
||||
minWidth: timestamp ? 200 : 100,
|
||||
resizable: !timestamp
|
||||
};
|
||||
};
|
||||
})}
|
||||
defaultPageSize={50}
|
||||
showPagination={false}
|
||||
|
@ -2348,6 +2353,35 @@ export class LoadDataView extends React.Component<LoadDataViewProps, LoadDataVie
|
|||
}
|
||||
|
||||
// ==================================================================
|
||||
private getSupervisorJson = async (): Promise<void> => {
|
||||
try {
|
||||
const resp = await axios.get(`/druid/indexer/v1/supervisor/${this.props.initSupervisorId}`);
|
||||
this.updateSpec(resp.data);
|
||||
this.updateStage('json-spec');
|
||||
} catch (e) {
|
||||
AppToaster.show({
|
||||
message: `Failed to get supervisor spec: ${getDruidErrorMessage(e)}`,
|
||||
intent: Intent.DANGER
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getTaskJson = async (): Promise<void> => {
|
||||
try {
|
||||
const resp = await axios.get(`/druid/indexer/v1/task/${this.props.initTaskId}`);
|
||||
this.updateSpec(resp.data.payload.spec);
|
||||
this.updateStage('json-spec');
|
||||
} catch (e) {
|
||||
AppToaster.show({
|
||||
message: `Failed to get task spec: ${getDruidErrorMessage(e)}`,
|
||||
intent: Intent.DANGER
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
renderLoading() {
|
||||
return <Loader loading/>;
|
||||
}
|
||||
|
||||
renderJsonSpecStage() {
|
||||
const { goToTask } = this.props;
|
||||
|
|
|
@ -48,7 +48,7 @@ export interface TasksViewProps extends React.Props<any> {
|
|||
openDialog: string | null;
|
||||
goToSql: (initSql: string) => void;
|
||||
goToMiddleManager: (middleManager: string) => void;
|
||||
goToLoadDataView: () => void;
|
||||
goToLoadDataView: (supervisorId?: string, taskId?: string) => void;
|
||||
noSqlMode: boolean;
|
||||
}
|
||||
|
||||
|
@ -286,9 +286,17 @@ ORDER BY "rank" DESC, "created_time" DESC`);
|
|||
this.taskQueryManager.rerunLastQuery();
|
||||
}
|
||||
|
||||
private getSupervisorActions(id: string, supervisorSuspended: boolean): BasicAction[] {
|
||||
return [
|
||||
{
|
||||
private getSupervisorActions(id: string, supervisorSuspended: boolean, type: string): BasicAction[] {
|
||||
const actions: BasicAction[] = [];
|
||||
if (type === 'kafka' || type === 'kinesis') {
|
||||
actions.push(
|
||||
{
|
||||
icon: IconNames.CLOUD_UPLOAD,
|
||||
title: 'Open in data loader',
|
||||
onAction: () => this.props.goToLoadDataView(id)
|
||||
});
|
||||
}
|
||||
actions.push({
|
||||
icon: IconNames.STEP_BACKWARD,
|
||||
title: 'Reset',
|
||||
onAction: () => this.setState({ resetSupervisorId: id })
|
||||
|
@ -304,7 +312,9 @@ ORDER BY "rank" DESC, "created_time" DESC`);
|
|||
intent: Intent.DANGER,
|
||||
onAction: () => this.setState({ terminateSupervisorId: id })
|
||||
}
|
||||
];
|
||||
);
|
||||
// @ts-ignore
|
||||
return actions;
|
||||
}
|
||||
|
||||
renderResumeSupervisorAction() {
|
||||
|
@ -413,7 +423,6 @@ ORDER BY "rank" DESC, "created_time" DESC`);
|
|||
renderSupervisorTable() {
|
||||
const { supervisors, supervisorsLoading, supervisorsError } = this.state;
|
||||
const { supervisorTableColumnSelectionHandler } = this;
|
||||
|
||||
return <>
|
||||
<ReactTable
|
||||
data={supervisors || []}
|
||||
|
@ -477,8 +486,9 @@ ORDER BY "rank" DESC, "created_time" DESC`);
|
|||
filterable: false,
|
||||
Cell: row => {
|
||||
const id = row.value;
|
||||
const type = row.row.type;
|
||||
const supervisorSuspended = row.original.spec.suspended;
|
||||
const supervisorActions = this.getSupervisorActions(id, supervisorSuspended);
|
||||
const supervisorActions = this.getSupervisorActions(id, supervisorSuspended, type);
|
||||
const supervisorMenu = basicActionsToMenu(supervisorActions);
|
||||
|
||||
return <ActionCell>
|
||||
|
@ -510,18 +520,24 @@ ORDER BY "rank" DESC, "created_time" DESC`);
|
|||
</>;
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
private getTaskActions(id: string, status: string): BasicAction[] {
|
||||
if (status !== 'RUNNING' && status !== 'WAITING' && status !== 'PENDING') return [];
|
||||
return [
|
||||
{
|
||||
private getTaskActions(id: string, status: string, type: string): BasicAction[] {
|
||||
const actions: BasicAction[] = [];
|
||||
if (type === 'index' || type === 'index_parallel') {
|
||||
actions.push({
|
||||
icon: IconNames.CLOUD_UPLOAD,
|
||||
title: 'Open in data loader',
|
||||
onAction: () => this.props.goToLoadDataView(undefined, id)
|
||||
});
|
||||
}
|
||||
if (status === 'RUNNING' || status === 'WAITING' || status === 'PENDING') {
|
||||
actions.push({
|
||||
icon: IconNames.CROSS,
|
||||
title: 'Kill',
|
||||
intent: Intent.DANGER,
|
||||
onAction: () => this.setState({ killTaskId: id })
|
||||
}
|
||||
];
|
||||
onAction: () => this.setState({killTaskId: id})
|
||||
});
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
renderKillTaskAction() {
|
||||
|
@ -657,8 +673,9 @@ ORDER BY "rank" DESC, "created_time" DESC`);
|
|||
Cell: row => {
|
||||
if (row.aggregated) return '';
|
||||
const id = row.value;
|
||||
const type = row.row.type;
|
||||
const { status } = row.original;
|
||||
const taskActions = this.getTaskActions(id, status);
|
||||
const taskActions = this.getTaskActions(id, status, type);
|
||||
const taskMenu = basicActionsToMenu(taskActions);
|
||||
|
||||
return <ActionCell>
|
||||
|
@ -688,11 +705,11 @@ ORDER BY "rank" DESC, "created_time" DESC`);
|
|||
</>;
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const { goToSql, goToLoadDataView, noSqlMode } = this.props;
|
||||
const { groupTasksBy, supervisorSpecDialogOpen, taskSpecDialogOpen, alertErrorMsg, taskTableActionDialogId, taskTableActionDialogActions, supervisorTableActionDialogId, supervisorTableActionDialogActions } = this.state;
|
||||
const { supervisorTableColumnSelectionHandler, taskTableColumnSelectionHandler } = this;
|
||||
|
||||
const submitTaskMenu = <Menu>
|
||||
<MenuItem
|
||||
text="Raw JSON task"
|
||||
|
|
Loading…
Reference in New Issue