From 469f36e8625ff445c234e371ff5f6f11dc51f12f Mon Sep 17 00:00:00 2001 From: lokesh-lingarajan Date: Fri, 18 Mar 2022 12:53:31 -0700 Subject: [PATCH] Setting default refresh value for task view as none. (#88) As part of this we added a default parameter that can be passed for refresh widget to avoid every refresh widget getting affected. --- .../refresh-button/refresh-button.tsx | 5 +- .../views/ingestion-view/ingestion-view.tsx | 1263 +++++++++++++++++ 2 files changed, 1267 insertions(+), 1 deletion(-) create mode 100644 web-console/src/views/ingestion-view/ingestion-view.tsx diff --git a/web-console/src/components/refresh-button/refresh-button.tsx b/web-console/src/components/refresh-button/refresh-button.tsx index bbf7ed2f0714..5a5c65151d3e 100644 --- a/web-console/src/components/refresh-button/refresh-button.tsx +++ b/web-console/src/components/refresh-button/refresh-button.tsx @@ -35,13 +35,16 @@ const DELAYS: DelayLabel[] = [ export interface RefreshButtonProps { onRefresh(auto: boolean): void; localStorageKey?: LocalStorageKeys; + defaultDelay?: number; } export const RefreshButton = React.memo(function RefreshButton(props: RefreshButtonProps) { + const { onRefresh, localStorageKey, defaultDelay = 30000 } = props; + return ( ; + + resumeSupervisorId?: string; + suspendSupervisorId?: string; + resetSupervisorId?: string; + terminateSupervisorId?: string; + + showResumeAllSupervisors: boolean; + showSuspendAllSupervisors: boolean; + showTerminateAllSupervisors: boolean; + + tasksState: QueryState; + + taskFilter: Filter[]; + supervisorFilter: Filter[]; + + groupTasksBy?: 'group_id' | 'type' | 'datasource' | 'status'; + + killTaskId?: string; + + supervisorSpecDialogOpen: boolean; + taskSpecDialogOpen: boolean; + alertErrorMsg?: string; + + taskTableActionDialogId?: string; + taskTableActionDialogStatus?: string; + taskTableActionDialogActions: BasicAction[]; + supervisorTableActionDialogId?: string; + supervisorTableActionDialogActions: BasicAction[]; + hiddenTaskColumns: LocalStorageBackedVisibility; + hiddenSupervisorColumns: LocalStorageBackedVisibility; +} + +function statusToColor(status: string): string { + switch (status) { + case 'RUNNING': + return '#2167d5'; + case 'WAITING': + return '#d5631a'; + case 'PENDING': + return '#ffbf00'; + case 'SUCCESS': + return '#57d500'; + case 'FAILED': + return '#d5100a'; + case 'CANCELED': + return '#858585'; + default: + return '#0a1500'; + } +} + +function stateToColor(status: string): string { + switch (status) { + case 'UNHEALTHY_SUPERVISOR': + return '#d5100a'; + case 'UNHEALTHY_TASKS': + return '#d5100a'; + case 'PENDING': + return '#ffbf00'; + case `SUSPENDED`: + return '#ffbf00'; + case 'STOPPING': + return '#d5100a'; + case 'RUNNING': + return '#2167d5'; + default: + return '#0a1500'; + } +} + +export class IngestionView extends React.PureComponent { + private readonly supervisorQueryManager: QueryManager; + private readonly taskQueryManager: QueryManager; + static statusRanking: Record = { + RUNNING: 4, + PENDING: 3, + WAITING: 2, + SUCCESS: 1, + FAILED: 1, + }; + + static SUPERVISOR_SQL = `SELECT + "supervisor_id", "type", "source", "state", "detailed_state", "suspended" = 1 AS "suspended" +FROM sys.supervisors +ORDER BY "supervisor_id"`; + + static TASK_SQL = `WITH tasks AS (SELECT + "task_id", "group_id", "type", "datasource", "created_time", "location", "duration", "error_msg", + CASE WHEN "error_msg" = '${CANCELED_ERROR_MSG}' THEN 'CANCELED' WHEN "status" = 'RUNNING' THEN "runner_status" ELSE "status" END AS "status" + FROM sys.tasks +) +SELECT "task_id", "group_id", "type", "datasource", "created_time", "location", "duration", "error_msg", "status" +FROM tasks +ORDER BY + ( + CASE "status" + WHEN 'RUNNING' THEN 4 + WHEN 'PENDING' THEN 3 + WHEN 'WAITING' THEN 2 + ELSE 1 + END + ) DESC, + "created_time" DESC`; + + constructor(props: IngestionViewProps, context: any) { + super(props, context); + + const taskFilter: Filter[] = []; + if (props.taskId) taskFilter.push({ id: 'task_id', value: `=${props.taskId}` }); + if (props.taskGroupId) taskFilter.push({ id: 'group_id', value: `=${props.taskGroupId}` }); + if (props.datasourceId) taskFilter.push({ id: 'datasource', value: `=${props.datasourceId}` }); + + const supervisorFilter: Filter[] = []; + if (props.datasourceId) + supervisorFilter.push({ id: 'datasource', value: `=${props.datasourceId}` }); + + this.state = { + supervisorsState: QueryState.INIT, + + showResumeAllSupervisors: false, + showSuspendAllSupervisors: false, + showTerminateAllSupervisors: false, + + tasksState: QueryState.INIT, + taskFilter: taskFilter, + supervisorFilter: supervisorFilter, + + supervisorSpecDialogOpen: props.openDialog === 'supervisor', + taskSpecDialogOpen: props.openDialog === 'task', + + taskTableActionDialogActions: [], + supervisorTableActionDialogActions: [], + + hiddenTaskColumns: new LocalStorageBackedVisibility( + LocalStorageKeys.TASK_TABLE_COLUMN_SELECTION, + ), + hiddenSupervisorColumns: new LocalStorageBackedVisibility( + LocalStorageKeys.SUPERVISOR_TABLE_COLUMN_SELECTION, + ), + }; + + this.supervisorQueryManager = new QueryManager({ + processQuery: async capabilities => { + if (capabilities.hasSql()) { + return await queryDruidSql({ + query: IngestionView.SUPERVISOR_SQL, + }); + } else if (capabilities.hasOverlordAccess()) { + const supervisors = (await Api.instance.get('/druid/indexer/v1/supervisor?full')).data; + if (!Array.isArray(supervisors)) throw new Error(`Unexpected results`); + return supervisors.map((sup: any) => { + return { + supervisor_id: deepGet(sup, 'id'), + type: deepGet(sup, 'spec.tuningConfig.type'), + source: + deepGet(sup, 'spec.ioConfig.topic') || + deepGet(sup, 'spec.ioConfig.stream') || + 'n/a', + state: deepGet(sup, 'state'), + detailed_state: deepGet(sup, 'detailedState'), + suspended: Boolean(deepGet(sup, 'suspended')), + }; + }); + } else { + throw new Error(`must have SQL or overlord access`); + } + }, + onStateChange: supervisorsState => { + this.setState({ + supervisorsState, + }); + }, + }); + + this.taskQueryManager = new QueryManager({ + processQuery: async capabilities => { + if (capabilities.hasSql()) { + return await queryDruidSql({ + query: IngestionView.TASK_SQL, + }); + } else if (capabilities.hasOverlordAccess()) { + const resp = await Api.instance.get(`/druid/indexer/v1/tasks`); + return IngestionView.parseTasks(resp.data); + } else { + throw new Error(`must have SQL or overlord access`); + } + }, + onStateChange: tasksState => { + this.setState({ + tasksState, + }); + }, + }); + } + + static parseTasks = (data: any[]): TaskQueryResultRow[] => { + return data.map(d => { + return { + task_id: d.id, + group_id: d.groupId, + type: d.type, + created_time: d.createdTime, + datasource: d.dataSource, + duration: d.duration ? d.duration : 0, + error_msg: d.errorMsg, + location: d.location.host ? `${d.location.host}:${d.location.port}` : null, + status: d.statusCode === 'RUNNING' ? d.runnerStatusCode : d.statusCode, + }; + }); + }; + + private static onSecondaryPaneSizeChange(secondaryPaneSize: number) { + localStorageSet(LocalStorageKeys.INGESTION_VIEW_PANE_SIZE, String(secondaryPaneSize)); + } + + componentDidMount(): void { + const { capabilities } = this.props; + + this.supervisorQueryManager.runQuery(capabilities); + this.taskQueryManager.runQuery(capabilities); + } + + componentWillUnmount(): void { + this.supervisorQueryManager.terminate(); + this.taskQueryManager.terminate(); + } + + private readonly closeSpecDialogs = () => { + this.setState({ + supervisorSpecDialogOpen: false, + taskSpecDialogOpen: false, + }); + }; + + private readonly submitSupervisor = async (spec: JSON) => { + try { + await Api.instance.post('/druid/indexer/v1/supervisor', spec); + } catch (e) { + AppToaster.show({ + message: `Failed to submit supervisor: ${getDruidErrorMessage(e)}`, + intent: Intent.DANGER, + }); + return; + } + + AppToaster.show({ + message: 'Supervisor submitted successfully', + intent: Intent.SUCCESS, + }); + this.supervisorQueryManager.rerunLastQuery(); + }; + + private readonly submitTask = async (spec: JSON) => { + try { + await Api.instance.post('/druid/indexer/v1/task', spec); + } catch (e) { + AppToaster.show({ + message: `Failed to submit task: ${getDruidErrorMessage(e)}`, + intent: Intent.DANGER, + }); + return; + } + + AppToaster.show({ + message: 'Task submitted successfully', + intent: Intent.SUCCESS, + }); + this.taskQueryManager.rerunLastQuery(); + }; + + private getSupervisorActions( + id: string, + supervisorSuspended: boolean, + type: string, + ): BasicAction[] { + const { goToDatasource, goToStreamingDataLoader } = this.props; + + const actions: BasicAction[] = []; + if (oneOf(type, 'kafka', 'kinesis')) { + actions.push( + { + icon: IconNames.MULTI_SELECT, + title: 'Go to datasource', + onAction: () => goToDatasource(id), + }, + { + icon: IconNames.CLOUD_UPLOAD, + title: 'Open in data loader', + onAction: () => goToStreamingDataLoader(id), + }, + ); + } + actions.push( + { + icon: supervisorSuspended ? IconNames.PLAY : IconNames.PAUSE, + title: supervisorSuspended ? 'Resume' : 'Suspend', + onAction: () => + supervisorSuspended + ? this.setState({ resumeSupervisorId: id }) + : this.setState({ suspendSupervisorId: id }), + }, + { + icon: IconNames.STEP_BACKWARD, + title: 'Hard reset', + intent: Intent.DANGER, + onAction: () => this.setState({ resetSupervisorId: id }), + }, + { + icon: IconNames.CROSS, + title: 'Terminate', + intent: Intent.DANGER, + onAction: () => this.setState({ terminateSupervisorId: id }), + }, + ); + return actions; + } + + renderResumeSupervisorAction() { + const { resumeSupervisorId } = this.state; + if (!resumeSupervisorId) return; + + return ( + { + const resp = await Api.instance.post( + `/druid/indexer/v1/supervisor/${Api.encodePath(resumeSupervisorId)}/resume`, + {}, + ); + return resp.data; + }} + confirmButtonText="Resume supervisor" + successText="Supervisor has been resumed" + failText="Could not resume supervisor" + intent={Intent.PRIMARY} + onClose={() => { + this.setState({ resumeSupervisorId: undefined }); + }} + onSuccess={() => { + this.supervisorQueryManager.rerunLastQuery(); + }} + > +

{`Are you sure you want to resume supervisor '${resumeSupervisorId}'?`}

+
+ ); + } + + renderSuspendSupervisorAction() { + const { suspendSupervisorId } = this.state; + if (!suspendSupervisorId) return; + + return ( + { + const resp = await Api.instance.post( + `/druid/indexer/v1/supervisor/${Api.encodePath(suspendSupervisorId)}/suspend`, + {}, + ); + return resp.data; + }} + confirmButtonText="Suspend supervisor" + successText="Supervisor has been suspended" + failText="Could not suspend supervisor" + intent={Intent.DANGER} + onClose={() => { + this.setState({ suspendSupervisorId: undefined }); + }} + onSuccess={() => { + this.supervisorQueryManager.rerunLastQuery(); + }} + > +

{`Are you sure you want to suspend supervisor '${suspendSupervisorId}'?`}

+
+ ); + } + + renderResetSupervisorAction() { + const { resetSupervisorId } = this.state; + if (!resetSupervisorId) return; + + return ( + { + const resp = await Api.instance.post( + `/druid/indexer/v1/supervisor/${Api.encodePath(resetSupervisorId)}/reset`, + {}, + ); + return resp.data; + }} + confirmButtonText="Hard reset supervisor" + successText="Supervisor has been hard reset" + failText="Could not hard reset supervisor" + intent={Intent.DANGER} + onClose={() => { + this.setState({ resetSupervisorId: undefined }); + }} + onSuccess={() => { + this.supervisorQueryManager.rerunLastQuery(); + }} + warningChecks={[ + `I understand that resetting ${resetSupervisorId} will clear checkpoints and therefore lead to data loss or duplication.`, + 'I understand that this operation cannot be undone.', + ]} + > +

{`Are you sure you want to hard reset supervisor '${resetSupervisorId}'?`}

+

Hard resetting a supervisor will lead to data loss or data duplication.

+

+ The reason for using this operation is to recover from a state in which the supervisor + ceases operating due to missing offsets. +

+
+ ); + } + + renderTerminateSupervisorAction() { + const { terminateSupervisorId } = this.state; + if (!terminateSupervisorId) return; + + return ( + { + const resp = await Api.instance.post( + `/druid/indexer/v1/supervisor/${Api.encodePath(terminateSupervisorId)}/terminate`, + {}, + ); + return resp.data; + }} + confirmButtonText="Terminate supervisor" + successText="Supervisor has been terminated" + failText="Could not terminate supervisor" + intent={Intent.DANGER} + onClose={() => { + this.setState({ terminateSupervisorId: undefined }); + }} + onSuccess={() => { + this.supervisorQueryManager.rerunLastQuery(); + }} + > +

{`Are you sure you want to terminate supervisor '${terminateSupervisorId}'?`}

+

This action is not reversible.

+
+ ); + } + + private renderSupervisorFilterableCell(field: string) { + const { supervisorFilter } = this.state; + + return (row: { value: any }) => ( + this.setState({ supervisorFilter: filters })} + > + {row.value} + + ); + } + + private onSupervisorDetail(supervisor: SupervisorQueryResultRow) { + this.setState({ + supervisorTableActionDialogId: supervisor.supervisor_id, + supervisorTableActionDialogActions: this.getSupervisorActions( + supervisor.supervisor_id, + supervisor.suspended, + supervisor.type, + ), + }); + } + + private renderSupervisorTable() { + const { supervisorsState, hiddenSupervisorColumns, taskFilter, supervisorFilter } = this.state; + + const supervisors = supervisorsState.data || []; + return ( + { + this.setState({ + supervisorFilter: filtered, + taskFilter: + column.id === 'datasource' + ? syncFilterClauseById(taskFilter, filtered, 'datasource') + : taskFilter, + }); + }} + filterable + defaultPageSize={SMALL_TABLE_PAGE_SIZE} + pageSizeOptions={SMALL_TABLE_PAGE_SIZE_OPTIONS} + showPagination={supervisors.length > SMALL_TABLE_PAGE_SIZE} + columns={[ + { + Header: 'Datasource', + id: 'datasource', + accessor: 'supervisor_id', + width: 300, + show: hiddenSupervisorColumns.shown('Datasource'), + Cell: ({ value, original }) => ( + this.onSupervisorDetail(original)} + hoverIcon={IconNames.EDIT} + > + {value} + + ), + }, + { + Header: 'Type', + accessor: 'type', + width: 100, + Cell: this.renderSupervisorFilterableCell('type'), + show: hiddenSupervisorColumns.shown('Type'), + }, + { + Header: 'Topic/Stream', + accessor: 'source', + width: 300, + Cell: this.renderSupervisorFilterableCell('source'), + show: hiddenSupervisorColumns.shown('Topic/Stream'), + }, + { + Header: 'Status', + id: 'status', + width: 300, + accessor: 'detailed_state', + Cell: row => ( + this.setState({ supervisorFilter: filters })} + > + + ●  + {row.value} + + + ), + show: hiddenSupervisorColumns.shown('Status'), + }, + { + Header: ACTION_COLUMN_LABEL, + id: ACTION_COLUMN_ID, + accessor: 'supervisor_id', + width: ACTION_COLUMN_WIDTH, + filterable: false, + Cell: row => { + const id = row.value; + const type = row.original.type; + const supervisorSuspended = row.original.suspended; + const supervisorActions = this.getSupervisorActions(id, supervisorSuspended, type); + return ( + this.onSupervisorDetail(row.original)} + actions={supervisorActions} + /> + ); + }, + show: hiddenSupervisorColumns.shown(ACTION_COLUMN_LABEL), + }, + ]} + /> + ); + } + + private getTaskActions( + id: string, + datasource: string, + status: string, + type: string, + ): BasicAction[] { + const { goToDatasource, goToClassicBatchDataLoader } = this.props; + + const actions: BasicAction[] = []; + if (datasource && status === 'SUCCESS') { + actions.push({ + icon: IconNames.MULTI_SELECT, + title: 'Go to datasource', + onAction: () => goToDatasource(datasource), + }); + } + if (oneOf(type, 'index', 'index_parallel')) { + actions.push({ + icon: IconNames.CLOUD_UPLOAD, + title: 'Open in data loader', + onAction: () => goToClassicBatchDataLoader(id), + }); + } + if (oneOf(status, 'RUNNING', 'WAITING', 'PENDING')) { + actions.push({ + icon: IconNames.CROSS, + title: 'Kill', + intent: Intent.DANGER, + onAction: () => this.setState({ killTaskId: id }), + }); + } + return actions; + } + + renderKillTaskAction() { + const { killTaskId } = this.state; + if (!killTaskId) return; + + return ( + { + const resp = await Api.instance.post( + `/druid/indexer/v1/task/${Api.encodePath(killTaskId)}/shutdown`, + {}, + ); + return resp.data; + }} + confirmButtonText="Kill task" + successText="Task was killed" + failText="Could not kill task" + intent={Intent.DANGER} + onClose={() => { + this.setState({ killTaskId: undefined }); + }} + onSuccess={() => { + this.taskQueryManager.rerunLastQuery(); + }} + > +

{`Are you sure you want to kill task '${killTaskId}'?`}

+
+ ); + } + + private renderTaskFilterableCell(field: string) { + const { taskFilter } = this.state; + + return (row: { value: any }) => ( + this.setState({ taskFilter: filters })} + > + {row.value} + + ); + } + + private onTaskDetail(task: TaskQueryResultRow) { + this.setState({ + taskTableActionDialogId: task.task_id, + taskTableActionDialogStatus: task.status, + taskTableActionDialogActions: this.getTaskActions( + task.task_id, + task.datasource, + task.status, + task.type, + ), + }); + } + + private renderTaskTable() { + const { tasksState, taskFilter, groupTasksBy, hiddenTaskColumns, supervisorFilter } = + this.state; + + const tasks = tasksState.data || []; + return ( + { + this.setState({ + supervisorFilter: + column.id === 'datasource' + ? syncFilterClauseById(supervisorFilter, filtered, 'datasource') + : supervisorFilter, + taskFilter: filtered, + }); + }} + defaultSorted={[{ id: 'status', desc: true }]} + pivotBy={groupTasksBy ? [groupTasksBy] : []} + defaultPageSize={SMALL_TABLE_PAGE_SIZE} + pageSizeOptions={SMALL_TABLE_PAGE_SIZE_OPTIONS} + showPagination={tasks.length > SMALL_TABLE_PAGE_SIZE} + columns={[ + { + Header: 'Task ID', + accessor: 'task_id', + width: 440, + Cell: ({ value, original }) => ( + this.onTaskDetail(original)} + hoverIcon={IconNames.EDIT} + > + {value} + + ), + Aggregated: () => '', + show: hiddenTaskColumns.shown('Task ID'), + }, + { + Header: 'Group ID', + accessor: 'group_id', + width: 300, + Cell: this.renderTaskFilterableCell('group_id'), + Aggregated: () => '', + show: hiddenTaskColumns.shown('Group ID'), + }, + { + Header: 'Type', + accessor: 'type', + width: 140, + Cell: this.renderTaskFilterableCell('type'), + show: hiddenTaskColumns.shown('Type'), + }, + { + Header: 'Datasource', + accessor: 'datasource', + width: 200, + Cell: this.renderTaskFilterableCell('datasource'), + show: hiddenTaskColumns.shown('Datasource'), + }, + { + Header: 'Status', + id: 'status', + width: 110, + accessor: row => ({ + status: row.status, + created_time: row.created_time, + toString: () => row.status, + }), + Cell: row => { + if (row.aggregated) return ''; + const { status } = row.original; + const errorMsg = row.original.error_msg; + return ( + this.setState({ taskFilter: filters })} + > + + ●  + {status} + {errorMsg && errorMsg !== CANCELED_ERROR_MSG && ( + this.setState({ alertErrorMsg: errorMsg })} + title={errorMsg} + > +  ? + + )} + + + ); + }, + sortMethod: (d1, d2) => { + const typeofD1 = typeof d1; + const typeofD2 = typeof d2; + if (typeofD1 !== typeofD2) return 0; + switch (typeofD1) { + case 'string': + return IngestionView.statusRanking[d1] - IngestionView.statusRanking[d2]; + + case 'object': + return ( + IngestionView.statusRanking[d1.status] - + IngestionView.statusRanking[d2.status] || + d1.created_time.localeCompare(d2.created_time) + ); + + default: + return 0; + } + }, + show: hiddenTaskColumns.shown('Status'), + }, + { + Header: 'Created time', + accessor: 'created_time', + width: 190, + Cell: this.renderTaskFilterableCell('created_time'), + Aggregated: () => '', + show: hiddenTaskColumns.shown('Created time'), + }, + { + Header: 'Duration', + accessor: 'duration', + width: 80, + filterable: false, + className: 'padded', + Cell({ value, original, aggregated }) { + if (aggregated) return ''; + if (value > 0) { + return formatDuration(value); + } + if (oneOf(original.status, 'RUNNING', 'PENDING') && original.created_time) { + // Compute running duration from the created time if it exists + return formatDuration(Date.now() - Date.parse(original.created_time)); + } + return ''; + }, + Aggregated: () => '', + show: hiddenTaskColumns.shown('Duration'), + }, + { + Header: 'Location', + accessor: 'location', + width: 200, + Cell: this.renderTaskFilterableCell('location'), + Aggregated: () => '', + show: hiddenTaskColumns.shown('Location'), + }, + { + Header: ACTION_COLUMN_LABEL, + id: ACTION_COLUMN_ID, + accessor: 'task_id', + width: ACTION_COLUMN_WIDTH, + filterable: false, + Cell: row => { + if (row.aggregated) return ''; + const id = row.value; + const type = row.row.type; + const { datasource, status } = row.original; + const taskActions = this.getTaskActions(id, datasource, status, type); + return ( + this.onTaskDetail(row.original)} + actions={taskActions} + /> + ); + }, + Aggregated: () => '', + show: hiddenTaskColumns.shown(ACTION_COLUMN_LABEL), + }, + ]} + /> + ); + } + + renderBulkSupervisorActions() { + const { capabilities, goToQuery } = this.props; + + return ( + <> + + {capabilities.hasSql() && ( + goToQuery({ queryString: IngestionView.SUPERVISOR_SQL })} + /> + )} + this.setState({ supervisorSpecDialogOpen: true })} + /> + this.setState({ showResumeAllSupervisors: true })} + /> + this.setState({ showSuspendAllSupervisors: true })} + /> + this.setState({ showTerminateAllSupervisors: true })} + /> + + {this.renderResumeAllSupervisorAction()} + {this.renderSuspendAllSupervisorAction()} + {this.renderTerminateAllSupervisorAction()} + + ); + } + + renderResumeAllSupervisorAction() { + const { showResumeAllSupervisors } = this.state; + if (!showResumeAllSupervisors) return; + + return ( + { + const resp = await Api.instance.post(`/druid/indexer/v1/supervisor/resumeAll`, {}); + return resp.data; + }} + confirmButtonText="Resume all supervisors" + successText="All supervisors have been resumed" + failText="Could not resume all supervisors" + intent={Intent.PRIMARY} + onClose={() => { + this.setState({ showResumeAllSupervisors: false }); + }} + onSuccess={() => { + this.supervisorQueryManager.rerunLastQuery(); + }} + > +

Are you sure you want to resume all the supervisors?

+
+ ); + } + + renderSuspendAllSupervisorAction() { + const { showSuspendAllSupervisors } = this.state; + if (!showSuspendAllSupervisors) return; + + return ( + { + const resp = await Api.instance.post(`/druid/indexer/v1/supervisor/suspendAll`, {}); + return resp.data; + }} + confirmButtonText="Suspend all supervisors" + successText="All supervisors have been suspended" + failText="Could not suspend all supervisors" + intent={Intent.DANGER} + onClose={() => { + this.setState({ showSuspendAllSupervisors: false }); + }} + onSuccess={() => { + this.supervisorQueryManager.rerunLastQuery(); + }} + > +

Are you sure you want to suspend all the supervisors?

+
+ ); + } + + renderTerminateAllSupervisorAction() { + const { showTerminateAllSupervisors } = this.state; + if (!showTerminateAllSupervisors) return; + + return ( + { + const resp = await Api.instance.post(`/druid/indexer/v1/supervisor/terminateAll`, {}); + return resp.data; + }} + confirmButtonText="Terminate all supervisors" + successText="All supervisors have been terminated" + failText="Could not terminate all supervisors" + intent={Intent.DANGER} + onClose={() => { + this.setState({ showTerminateAllSupervisors: false }); + }} + onSuccess={() => { + this.supervisorQueryManager.rerunLastQuery(); + }} + > +

Are you sure you want to terminate all the supervisors?

+
+ ); + } + + renderBulkTasksActions() { + const { goToQuery, capabilities } = this.props; + + return ( + + {capabilities.hasSql() && ( + goToQuery({ queryString: IngestionView.TASK_SQL })} + /> + )} + this.setState({ taskSpecDialogOpen: true })} + /> + + ); + } + + render(): JSX.Element { + const { + groupTasksBy, + supervisorSpecDialogOpen, + taskSpecDialogOpen, + alertErrorMsg, + taskTableActionDialogId, + taskTableActionDialogActions, + supervisorTableActionDialogId, + supervisorTableActionDialogActions, + taskTableActionDialogStatus, + hiddenSupervisorColumns, + hiddenTaskColumns, + } = this.state; + + return ( + <> + +
+ + { + if (auto && hasPopoverOpen()) return; + this.supervisorQueryManager.rerunLastQuery(auto); + }} + /> + {this.renderBulkSupervisorActions()} + + this.setState(prevState => ({ + hiddenSupervisorColumns: prevState.hiddenSupervisorColumns.toggle(column), + })) + } + tableColumnsHidden={hiddenSupervisorColumns.getHiddenColumns()} + /> + + {this.renderSupervisorTable()} +
+
+ + + + + + + + + + { + if (auto && hasPopoverOpen()) return; + this.taskQueryManager.rerunLastQuery(auto); + }} + /> + {this.renderBulkTasksActions()} + + this.setState(prevState => ({ + hiddenTaskColumns: prevState.hiddenTaskColumns.toggle(column), + })) + } + tableColumnsHidden={hiddenTaskColumns.getHiddenColumns()} + /> + + {this.renderTaskTable()} +
+
+ {this.renderResumeSupervisorAction()} + {this.renderSuspendSupervisorAction()} + {this.renderResetSupervisorAction()} + {this.renderTerminateSupervisorAction()} + {this.renderKillTaskAction()} + {supervisorSpecDialogOpen && ( + + )} + {taskSpecDialogOpen && ( + + )} + this.setState({ alertErrorMsg: undefined })} + > +

{alertErrorMsg}

+
+ {supervisorTableActionDialogId && ( + this.setState({ supervisorTableActionDialogId: undefined })} + /> + )} + {taskTableActionDialogId && taskTableActionDialogStatus && ( + this.setState({ taskTableActionDialogId: undefined })} + /> + )} + + ); + } +}