From 11a7e91a73ec9412af11d669cf02cf7765b83ff2 Mon Sep 17 00:00:00 2001 From: Qi Shu Date: Tue, 23 Apr 2019 16:15:02 -0700 Subject: [PATCH] No SQL mode in web console (#7493) * Added no sql mode * Use status code * Add no sql mode to server view * add sql broker check to decide if no sql mode should be enabled * Fix historicals in home view * Name change * Add types for query result; improved functions * Fixed a conflict/bug * Fixed a bug * multiple fix * removed unused imports * terminate query manager * fix wording --- web-console/src/console-application.tsx | 101 +++++++++++----- web-console/src/variables.ts | 1 + web-console/src/views/datasource-view.tsx | 54 +++++++-- web-console/src/views/home-view.tsx | 92 ++++++++++----- web-console/src/views/segments-view.tsx | 138 ++++++++++++++++++---- web-console/src/views/servers-view.tsx | 73 ++++++++++-- web-console/src/views/tasks-view.tsx | 70 +++++++++-- 7 files changed, 416 insertions(+), 113 deletions(-) diff --git a/web-console/src/console-application.tsx b/web-console/src/console-application.tsx index 4d106385169..4f5ea47cda0 100644 --- a/web-console/src/console-application.tsx +++ b/web-console/src/console-application.tsx @@ -24,8 +24,10 @@ import * as React from 'react'; import { HashRouter, Route, Switch } from 'react-router-dom'; import { HeaderActiveTab, HeaderBar } from './components/header-bar'; +import {Loader} from './components/loader'; import { AppToaster } from './singletons/toaster'; -import { DRUID_DOCS_SQL, LEGACY_COORDINATOR_CONSOLE, LEGACY_OVERLORD_CONSOLE } from './variables'; +import {QueryManager} from './utils'; +import {DRUID_DOCS_API, DRUID_DOCS_SQL, LEGACY_COORDINATOR_CONSOLE, LEGACY_OVERLORD_CONSOLE} from './variables'; import { DatasourcesView } from './views/datasource-view'; import { HomeView } from './views/home-view'; import { LookupsView } from './views/lookups-view'; @@ -45,45 +47,54 @@ export interface ConsoleApplicationProps extends React.Props { export interface ConsoleApplicationState { aboutDialogOpen: boolean; + noSqlMode: boolean; + capabilitiesLoading: boolean; } export class ConsoleApplication extends React.Component { static MESSAGE_KEY = 'druid-console-message'; static MESSAGE_DISMISSED = 'dismissed'; + private capabilitiesQueryManager: QueryManager; - static async ensureSql() { + static async discoverCapabilities(): Promise<'working-with-sql' | 'working-without-sql' | 'broken'> { try { await axios.post('/druid/v2/sql', { query: 'SELECT 1337' }); } catch (e) { const { response } = e; - if (response.status !== 405 || response.statusText !== 'Method Not Allowed') return true; // other failure + if (response.status !== 405 || response.statusText !== 'Method Not Allowed') return 'working-with-sql'; // other failure try { await axios.get('/status'); } catch (e) { - return true; // total failure + return 'broken'; // total failure } - // Status works but SQL 405s => the SQL endpoint is disabled - AppToaster.show({ - icon: IconNames.ERROR, - intent: Intent.DANGER, - timeout: 120000, - /* tslint:disable:jsx-alignment */ - message: <> - It appears that the SQL endpoint is disabled. Either enable the SQL endpoint or use the old coordinator and overlord consoles that do not rely on the SQL endpoint. - - /* tslint:enable:jsx-alignment */ - }); - return false; + return 'working-without-sql'; } - return true; + return 'working-with-sql'; } - static async shownNotifications() { - await ConsoleApplication.ensureSql(); + static shownNotifications(capabilities: string) { + let message: JSX.Element = <>; + /* tslint:disable:jsx-alignment */ + if (capabilities === 'working-without-sql') { + message = <> + It appears that the SQL endpoint is disabled. The console will fall back + to native Druid APIs and will be + limited in functionality. Look at the SQL docs to + enable the SQL endpoint. + ; + } else if (capabilities === 'broken') { + message = <> + It appears that the Druid is not responding. Data cannot be retrieved right now + ; + } + /* tslint:enable:jsx-alignment */ + AppToaster.show({ + icon: IconNames.ERROR, + intent: Intent.DANGER, + timeout: 120000, + message: message + }); } private taskId: string | null; @@ -95,7 +106,9 @@ export class ConsoleApplication extends React.Component { + const capabilities = await ConsoleApplication.discoverCapabilities(); + if (capabilities !== 'working-with-sql') { + ConsoleApplication.shownNotifications(capabilities); + } + return capabilities; + }, + onStateChange: ({ result, loading, error }) => { + this.setState({ + noSqlMode: result === 'working-with-sql' ? false : true, + capabilitiesLoading: loading + }); + } + }); } componentDidMount(): void { - ConsoleApplication.shownNotifications(); + this.capabilitiesQueryManager.runQuery('dummy'); + } + + componentWillUnmount(): void { + this.capabilitiesQueryManager.terminate(); } private resetInitialsDelay() { @@ -147,6 +180,7 @@ export class ConsoleApplication extends React.Component { return <> @@ -155,31 +189,40 @@ export class ConsoleApplication extends React.Component; }; + if (capabilitiesLoading) { + return
+ +
; + } + return
{ - return wrapInViewContainer('datasources', ); + return wrapInViewContainer('datasources', ); }} /> { - return wrapInViewContainer('segments', ); + return wrapInViewContainer('segments', ); }} /> { - return wrapInViewContainer('tasks', , true); + return wrapInViewContainer('tasks', , true); }} /> { - return wrapInViewContainer('servers', , true); + return wrapInViewContainer('servers', , true); }} /> { - return wrapInViewContainer(null, ); + return wrapInViewContainer(null, ); }} /> diff --git a/web-console/src/variables.ts b/web-console/src/variables.ts index b338bc29e7a..10a51ec9f27 100644 --- a/web-console/src/variables.ts +++ b/web-console/src/variables.ts @@ -26,3 +26,4 @@ export const DRUID_DOCS_SQL = 'http://druid.io/docs/latest/querying/sql.html'; export const DRUID_COMMUNITY = 'http://druid.io/community/'; export const DRUID_USER_GROUP = 'https://groups.google.com/forum/#!forum/druid-user'; export const DRUID_DEVELOPER_GROUP = 'https://lists.apache.org/list.html?dev@druid.apache.org'; +export const DRUID_DOCS_API = 'http://druid.io/docs/latest/operations/api-reference.html'; diff --git a/web-console/src/views/datasource-view.tsx b/web-console/src/views/datasource-view.tsx index a9061a0769d..406599ae567 100644 --- a/web-console/src/views/datasource-view.tsx +++ b/web-console/src/views/datasource-view.tsx @@ -44,10 +44,12 @@ import { import './datasource-view.scss'; const tableColumns: string[] = ['Datasource', 'Availability', 'Retention', 'Compaction', 'Size', 'Num rows', 'Actions']; +const tableColumnsNoSql: string[] = ['Datasource', 'Availability', 'Retention', 'Compaction', 'Size', 'Actions']; export interface DatasourcesViewProps extends React.Props { goToSql: (initSql: string) => void; goToSegments: (datasource: string, onlyUnavailable?: boolean) => void; + noSqlMode: boolean; } interface Datasource { @@ -56,6 +58,14 @@ interface Datasource { [key: string]: any; } +interface DatasourceQueryResultRow { + datasource: string; + num_available_segments: number; + num_rows: number; + num_segments: number; + size: number; +} + export interface DatasourcesViewState { datasourcesLoading: boolean; datasources: Datasource[] | null; @@ -116,9 +126,28 @@ export class DatasourcesView extends React.Component { - const datasources: any[] = await queryDruidSql({ query }); + let datasources: DatasourceQueryResultRow[]; + if (!noSqlMode) { + datasources = await queryDruidSql({ query }); + } else { + const datasourcesResp = await axios.get('/druid/coordinator/v1/datasources?simple'); + const loadstatusResp = await axios.get('/druid/coordinator/v1/loadstatus?simple'); + const loadstatus = loadstatusResp.data; + datasources = datasourcesResp.data.map((d: any) => { + return { + datasource: d.name, + num_available_segments: d.properties.segments.count, + size: d.properties.segments.size, + num_segments: d.properties.segments.count + loadstatus[d.name], + num_rows: -1 + }; + }); + } + const seen = countBy(datasources, (x: any) => x.datasource); const disabledResp = await axios.get('/druid/coordinator/v1/metadata/datasources?includeDisabled'); @@ -133,7 +162,7 @@ export class DatasourcesView extends React.Component ({ datasource: d, disabled: true }))); + const allDatasources = (datasources as any).concat(disabled.map(d => ({ datasource: d, disabled: true }))); allDatasources.forEach((ds: any) => { ds.rules = rules[ds.datasource] || []; ds.compaction = compaction[ds.datasource]; @@ -354,7 +383,7 @@ GROUP BY 1`); } renderDatasourceTable() { - const { goToSegments } = this.props; + const { goToSegments, noSqlMode } = this.props; const { datasources, defaultRules, datasourcesLoading, datasourcesError, datasourcesFilter, showDisabled } = this.state; const { tableColumnSelectionHandler } = this; let data = datasources || []; @@ -492,7 +521,7 @@ GROUP BY 1`); filterable: false, width: 100, Cell: (row) => formatNumber(row.value), - show: tableColumnSelectionHandler.showColumn('Num rows') + show: !noSqlMode && tableColumnSelectionHandler.showColumn('Num rows') }, { Header: 'Actions', @@ -529,7 +558,7 @@ GROUP BY 1`); } render() { - const { goToSql } = this.props; + const { goToSql, noSqlMode } = this.props; const { showDisabled } = this.state; const { tableColumnSelectionHandler } = this; @@ -540,18 +569,21 @@ GROUP BY 1`); text="Refresh" onClick={() => this.datasourceQueryManager.rerunLastQuery()} /> -