mirror of https://github.com/apache/druid.git
Web console: use SQL for the supervisor view (#8796)
* use SQL for supervisor view * home view sql also * no proxy mode * fix alert * improve message
This commit is contained in:
parent
27acdbd2b8
commit
f6028de7a8
|
@ -15,6 +15,7 @@ exports[`header bar matches snapshot 1`] = `
|
||||||
<Blueprint3.NavbarDivider />
|
<Blueprint3.NavbarDivider />
|
||||||
<Blueprint3.AnchorButton
|
<Blueprint3.AnchorButton
|
||||||
active={true}
|
active={true}
|
||||||
|
disabled={false}
|
||||||
href="#load-data"
|
href="#load-data"
|
||||||
icon="cloud-upload"
|
icon="cloud-upload"
|
||||||
intent="none"
|
intent="none"
|
||||||
|
@ -84,6 +85,7 @@ exports[`header bar matches snapshot 1`] = `
|
||||||
wrapperTagName="span"
|
wrapperTagName="span"
|
||||||
>
|
>
|
||||||
<Blueprint3.Button
|
<Blueprint3.Button
|
||||||
|
disabled={false}
|
||||||
icon="share"
|
icon="share"
|
||||||
minimal={true}
|
minimal={true}
|
||||||
text="Legacy"
|
text="Legacy"
|
||||||
|
@ -151,6 +153,7 @@ exports[`header bar matches snapshot 1`] = `
|
||||||
wrapperTagName="span"
|
wrapperTagName="span"
|
||||||
>
|
>
|
||||||
<Blueprint3.Button
|
<Blueprint3.Button
|
||||||
|
disabled={false}
|
||||||
icon="cog"
|
icon="cog"
|
||||||
minimal={true}
|
minimal={true}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -23,7 +23,9 @@ import { HeaderBar } from './header-bar';
|
||||||
|
|
||||||
describe('header bar', () => {
|
describe('header bar', () => {
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
const headerBar = shallow(<HeaderBar active={'load-data'} hideLegacy={false} />);
|
const headerBar = shallow(
|
||||||
|
<HeaderBar active={'load-data'} hideLegacy={false} capabilities="full" />,
|
||||||
|
);
|
||||||
expect(headerBar).toMatchSnapshot();
|
expect(headerBar).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -36,6 +36,7 @@ import { AboutDialog } from '../../dialogs/about-dialog/about-dialog';
|
||||||
import { CoordinatorDynamicConfigDialog } from '../../dialogs/coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog';
|
import { CoordinatorDynamicConfigDialog } from '../../dialogs/coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog';
|
||||||
import { DoctorDialog } from '../../dialogs/doctor-dialog/doctor-dialog';
|
import { DoctorDialog } from '../../dialogs/doctor-dialog/doctor-dialog';
|
||||||
import { OverlordDynamicConfigDialog } from '../../dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog';
|
import { OverlordDynamicConfigDialog } from '../../dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog';
|
||||||
|
import { Capabilities } from '../../utils/capabilities';
|
||||||
import {
|
import {
|
||||||
DRUID_ASF_SLACK,
|
DRUID_ASF_SLACK,
|
||||||
DRUID_DOCS,
|
DRUID_DOCS,
|
||||||
|
@ -130,10 +131,11 @@ function LegacyMenu() {
|
||||||
export interface HeaderBarProps {
|
export interface HeaderBarProps {
|
||||||
active: HeaderActiveTab;
|
active: HeaderActiveTab;
|
||||||
hideLegacy: boolean;
|
hideLegacy: boolean;
|
||||||
|
capabilities: Capabilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) {
|
export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) {
|
||||||
const { active, hideLegacy } = props;
|
const { active, hideLegacy, capabilities } = props;
|
||||||
const [aboutDialogOpen, setAboutDialogOpen] = useState(false);
|
const [aboutDialogOpen, setAboutDialogOpen] = useState(false);
|
||||||
const [doctorDialogOpen, setDoctorDialogOpen] = useState(false);
|
const [doctorDialogOpen, setDoctorDialogOpen] = useState(false);
|
||||||
const [coordinatorDynamicConfigDialogOpen, setCoordinatorDynamicConfigDialogOpen] = useState(
|
const [coordinatorDynamicConfigDialogOpen, setCoordinatorDynamicConfigDialogOpen] = useState(
|
||||||
|
@ -198,6 +200,7 @@ export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) {
|
||||||
href="#load-data"
|
href="#load-data"
|
||||||
minimal={!loadDataPrimary}
|
minimal={!loadDataPrimary}
|
||||||
intent={loadDataPrimary ? Intent.PRIMARY : Intent.NONE}
|
intent={loadDataPrimary ? Intent.PRIMARY : Intent.NONE}
|
||||||
|
disabled={capabilities === 'no-proxy'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
|
@ -241,12 +244,25 @@ export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) {
|
||||||
</NavbarGroup>
|
</NavbarGroup>
|
||||||
<NavbarGroup align={Alignment.RIGHT}>
|
<NavbarGroup align={Alignment.RIGHT}>
|
||||||
{!hideLegacy && (
|
{!hideLegacy && (
|
||||||
<Popover content={<LegacyMenu />} position={Position.BOTTOM_RIGHT}>
|
<Popover
|
||||||
<Button minimal icon={IconNames.SHARE} text="Legacy" />
|
content={<LegacyMenu />}
|
||||||
|
position={Position.BOTTOM_RIGHT}
|
||||||
|
disabled={capabilities === 'no-proxy'}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
minimal
|
||||||
|
icon={IconNames.SHARE}
|
||||||
|
text="Legacy"
|
||||||
|
disabled={capabilities === 'no-proxy'}
|
||||||
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
)}
|
)}
|
||||||
<Popover content={configMenu} position={Position.BOTTOM_RIGHT}>
|
<Popover
|
||||||
<Button minimal icon={IconNames.COG} />
|
content={configMenu}
|
||||||
|
position={Position.BOTTOM_RIGHT}
|
||||||
|
disabled={capabilities === 'no-proxy'}
|
||||||
|
>
|
||||||
|
<Button minimal icon={IconNames.COG} disabled={capabilities === 'no-proxy'} />
|
||||||
</Popover>
|
</Popover>
|
||||||
<Popover content={helpMenu} position={Position.BOTTOM_RIGHT}>
|
<Popover content={helpMenu} position={Position.BOTTOM_RIGHT}>
|
||||||
<Button minimal icon={IconNames.HELP} />
|
<Button minimal icon={IconNames.HELP} />
|
||||||
|
|
|
@ -26,7 +26,8 @@ import { HashRouter, Route, Switch } from 'react-router-dom';
|
||||||
import { ExternalLink, HeaderActiveTab, HeaderBar, Loader } from './components';
|
import { ExternalLink, HeaderActiveTab, HeaderBar, Loader } from './components';
|
||||||
import { AppToaster } from './singletons/toaster';
|
import { AppToaster } from './singletons/toaster';
|
||||||
import { localStorageGet, LocalStorageKeys, QueryManager } from './utils';
|
import { localStorageGet, LocalStorageKeys, QueryManager } from './utils';
|
||||||
import { DRUID_DOCS_API, DRUID_DOCS_SQL } from './variables';
|
import { Capabilities } from './utils/capabilities';
|
||||||
|
import { DRUID_DOCS_API, DRUID_DOCS_SQL, DRUID_DOCS_VERSION } from './variables';
|
||||||
import {
|
import {
|
||||||
DatasourcesView,
|
DatasourcesView,
|
||||||
HomeView,
|
HomeView,
|
||||||
|
@ -40,15 +41,13 @@ import {
|
||||||
|
|
||||||
import './console-application.scss';
|
import './console-application.scss';
|
||||||
|
|
||||||
type Capabilities = 'working-with-sql' | 'working-without-sql' | 'broken';
|
|
||||||
|
|
||||||
export interface ConsoleApplicationProps {
|
export interface ConsoleApplicationProps {
|
||||||
hideLegacy: boolean;
|
hideLegacy: boolean;
|
||||||
exampleManifestsUrl?: string;
|
exampleManifestsUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConsoleApplicationState {
|
export interface ConsoleApplicationState {
|
||||||
noSqlMode: boolean;
|
capabilities: Capabilities;
|
||||||
capabilitiesLoading: boolean;
|
capabilitiesLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +63,7 @@ export class ConsoleApplication extends React.PureComponent<
|
||||||
const capabilitiesOverride = localStorageGet(LocalStorageKeys.CAPABILITIES_OVERRIDE);
|
const capabilitiesOverride = localStorageGet(LocalStorageKeys.CAPABILITIES_OVERRIDE);
|
||||||
if (capabilitiesOverride) return capabilitiesOverride as Capabilities;
|
if (capabilitiesOverride) return capabilitiesOverride as Capabilities;
|
||||||
|
|
||||||
|
// Check SQL endpoint
|
||||||
try {
|
try {
|
||||||
await axios.post(
|
await axios.post(
|
||||||
'/druid/v2/sql',
|
'/druid/v2/sql',
|
||||||
|
@ -73,7 +73,7 @@ export class ConsoleApplication extends React.PureComponent<
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const { response } = e;
|
const { response } = e;
|
||||||
if (response.status !== 405 || response.statusText !== 'Method Not Allowed') {
|
if (response.status !== 405 || response.statusText !== 'Method Not Allowed') {
|
||||||
return 'working-with-sql'; // other failure
|
return 'full'; // other failure
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await axios.get('/status', { timeout: ConsoleApplication.STATUS_TIMEOUT });
|
await axios.get('/status', { timeout: ConsoleApplication.STATUS_TIMEOUT });
|
||||||
|
@ -81,27 +81,65 @@ export class ConsoleApplication extends React.PureComponent<
|
||||||
return 'broken'; // total failure
|
return 'broken'; // total failure
|
||||||
}
|
}
|
||||||
// Status works but SQL 405s => the SQL endpoint is disabled
|
// Status works but SQL 405s => the SQL endpoint is disabled
|
||||||
return 'working-without-sql';
|
return 'no-sql';
|
||||||
}
|
|
||||||
return 'working-with-sql';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static shownNotifications(capabilities: string) {
|
// Check proxy
|
||||||
let message: JSX.Element = <></>;
|
try {
|
||||||
|
await axios.get('/proxy/coordinator/status', { timeout: ConsoleApplication.STATUS_TIMEOUT });
|
||||||
|
} catch (e) {
|
||||||
|
const { response } = e;
|
||||||
|
if (response.status !== 404) {
|
||||||
|
console.log('response.statusText', response.statusText);
|
||||||
|
return 'full'; // other failure
|
||||||
|
}
|
||||||
|
return 'no-proxy';
|
||||||
|
}
|
||||||
|
|
||||||
if (capabilities === 'working-without-sql') {
|
return 'full';
|
||||||
|
}
|
||||||
|
|
||||||
|
static shownNotifications(capabilities: Capabilities) {
|
||||||
|
let message: JSX.Element;
|
||||||
|
switch (capabilities) {
|
||||||
|
case 'no-sql':
|
||||||
message = (
|
message = (
|
||||||
<>
|
<>
|
||||||
It appears that the SQL endpoint is disabled. The console will fall back to{' '}
|
It appears that the SQL endpoint is disabled. The console will fall back to{' '}
|
||||||
<ExternalLink href={DRUID_DOCS_API}>native Druid APIs</ExternalLink> and will be limited
|
<ExternalLink href={DRUID_DOCS_API}>native Druid APIs</ExternalLink> and will be limited
|
||||||
in functionality. Look at <ExternalLink href={DRUID_DOCS_SQL}>the SQL docs</ExternalLink>{' '}
|
in functionality. Look at{' '}
|
||||||
to enable the SQL endpoint.
|
<ExternalLink href={DRUID_DOCS_SQL}>the SQL docs</ExternalLink> to enable the SQL
|
||||||
|
endpoint.
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
} else if (capabilities === 'broken') {
|
break;
|
||||||
|
|
||||||
|
case 'no-proxy':
|
||||||
message = (
|
message = (
|
||||||
<>It appears that the Druid is not responding. Data cannot be retrieved right now</>
|
<>
|
||||||
|
It appears that the management proxy is not enabled, the console will operate with
|
||||||
|
limited functionality. Look at{' '}
|
||||||
|
<ExternalLink
|
||||||
|
href={`https://druid.apache.org/docs/${DRUID_DOCS_VERSION}/operations/management-uis.html#druid-console`}
|
||||||
|
>
|
||||||
|
the console docs
|
||||||
|
</ExternalLink>{' '}
|
||||||
|
for more info on how to enable the management proxy.
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'broken':
|
||||||
|
message = (
|
||||||
|
<>
|
||||||
|
It appears that the the Router node is not responding. The console will not function at
|
||||||
|
the moment
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
|
@ -123,21 +161,21 @@ export class ConsoleApplication extends React.PureComponent<
|
||||||
constructor(props: ConsoleApplicationProps, context: any) {
|
constructor(props: ConsoleApplicationProps, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
this.state = {
|
this.state = {
|
||||||
noSqlMode: false,
|
capabilities: 'full',
|
||||||
capabilitiesLoading: true,
|
capabilitiesLoading: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.capabilitiesQueryManager = new QueryManager({
|
this.capabilitiesQueryManager = new QueryManager({
|
||||||
processQuery: async () => {
|
processQuery: async () => {
|
||||||
const capabilities = await ConsoleApplication.discoverCapabilities();
|
const capabilities = await ConsoleApplication.discoverCapabilities();
|
||||||
if (capabilities !== 'working-with-sql') {
|
if (capabilities !== 'full') {
|
||||||
ConsoleApplication.shownNotifications(capabilities);
|
ConsoleApplication.shownNotifications(capabilities);
|
||||||
}
|
}
|
||||||
return capabilities;
|
return capabilities;
|
||||||
},
|
},
|
||||||
onStateChange: ({ result, loading }) => {
|
onStateChange: ({ result, loading }) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
noSqlMode: result !== 'working-with-sql',
|
capabilities: result || 'full',
|
||||||
capabilitiesLoading: loading,
|
capabilitiesLoading: loading,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -216,18 +254,19 @@ export class ConsoleApplication extends React.PureComponent<
|
||||||
classType: 'normal' | 'narrow-pad' = 'normal',
|
classType: 'normal' | 'narrow-pad' = 'normal',
|
||||||
) => {
|
) => {
|
||||||
const { hideLegacy } = this.props;
|
const { hideLegacy } = this.props;
|
||||||
|
const { capabilities } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HeaderBar active={active} hideLegacy={hideLegacy} />
|
<HeaderBar active={active} hideLegacy={hideLegacy} capabilities={capabilities} />
|
||||||
<div className={classNames('view-container', classType)}>{el}</div>
|
<div className={classNames('view-container', classType)}>{el}</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
private wrappedHomeView = () => {
|
private wrappedHomeView = () => {
|
||||||
const { noSqlMode } = this.state;
|
const { capabilities } = this.state;
|
||||||
return this.wrapInViewContainer(null, <HomeView noSqlMode={noSqlMode} />);
|
return this.wrapInViewContainer(null, <HomeView capabilities={capabilities} />);
|
||||||
};
|
};
|
||||||
|
|
||||||
private wrappedLoadDataView = () => {
|
private wrappedLoadDataView = () => {
|
||||||
|
@ -250,7 +289,7 @@ export class ConsoleApplication extends React.PureComponent<
|
||||||
};
|
};
|
||||||
|
|
||||||
private wrappedDatasourcesView = () => {
|
private wrappedDatasourcesView = () => {
|
||||||
const { noSqlMode } = this.state;
|
const { capabilities } = this.state;
|
||||||
return this.wrapInViewContainer(
|
return this.wrapInViewContainer(
|
||||||
'datasources',
|
'datasources',
|
||||||
<DatasourcesView
|
<DatasourcesView
|
||||||
|
@ -258,26 +297,26 @@ export class ConsoleApplication extends React.PureComponent<
|
||||||
goToQuery={this.goToQuery}
|
goToQuery={this.goToQuery}
|
||||||
goToTask={this.goToTaskWithDatasource}
|
goToTask={this.goToTaskWithDatasource}
|
||||||
goToSegments={this.goToSegments}
|
goToSegments={this.goToSegments}
|
||||||
noSqlMode={noSqlMode}
|
capabilities={capabilities}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
private wrappedSegmentsView = () => {
|
private wrappedSegmentsView = () => {
|
||||||
const { noSqlMode } = this.state;
|
const { capabilities } = this.state;
|
||||||
return this.wrapInViewContainer(
|
return this.wrapInViewContainer(
|
||||||
'segments',
|
'segments',
|
||||||
<SegmentsView
|
<SegmentsView
|
||||||
datasource={this.datasource}
|
datasource={this.datasource}
|
||||||
onlyUnavailable={this.onlyUnavailable}
|
onlyUnavailable={this.onlyUnavailable}
|
||||||
goToQuery={this.goToQuery}
|
goToQuery={this.goToQuery}
|
||||||
noSqlMode={noSqlMode}
|
capabilities={capabilities}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
private wrappedTasksView = () => {
|
private wrappedTasksView = () => {
|
||||||
const { noSqlMode } = this.state;
|
const { capabilities } = this.state;
|
||||||
return this.wrapInViewContainer(
|
return this.wrapInViewContainer(
|
||||||
'tasks',
|
'tasks',
|
||||||
<TasksView
|
<TasksView
|
||||||
|
@ -288,20 +327,20 @@ export class ConsoleApplication extends React.PureComponent<
|
||||||
goToQuery={this.goToQuery}
|
goToQuery={this.goToQuery}
|
||||||
goToMiddleManager={this.goToMiddleManager}
|
goToMiddleManager={this.goToMiddleManager}
|
||||||
goToLoadData={this.goToLoadData}
|
goToLoadData={this.goToLoadData}
|
||||||
noSqlMode={noSqlMode}
|
capabilities={capabilities}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
private wrappedServersView = () => {
|
private wrappedServersView = () => {
|
||||||
const { noSqlMode } = this.state;
|
const { capabilities } = this.state;
|
||||||
return this.wrapInViewContainer(
|
return this.wrapInViewContainer(
|
||||||
'servers',
|
'servers',
|
||||||
<ServersView
|
<ServersView
|
||||||
middleManager={this.middleManager}
|
middleManager={this.middleManager}
|
||||||
goToQuery={this.goToQuery}
|
goToQuery={this.goToQuery}
|
||||||
goToTask={this.goToTaskWithTaskId}
|
goToTask={this.goToTaskWithTaskId}
|
||||||
noSqlMode={noSqlMode}
|
capabilities={capabilities}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -316,7 +355,7 @@ export class ConsoleApplication extends React.PureComponent<
|
||||||
if (capabilitiesLoading) {
|
if (capabilitiesLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="loading-capabilities">
|
<div className="loading-capabilities">
|
||||||
<Loader loadingText="" loading={capabilitiesLoading} />
|
<Loader loadingText="" loading />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -326,13 +365,14 @@ export class ConsoleApplication extends React.PureComponent<
|
||||||
<div className="console-application">
|
<div className="console-application">
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/load-data" component={this.wrappedLoadDataView} />
|
<Route path="/load-data" component={this.wrappedLoadDataView} />
|
||||||
<Route path="/query" component={this.wrappedQueryView} />
|
|
||||||
|
|
||||||
<Route path="/datasources" component={this.wrappedDatasourcesView} />
|
<Route path="/datasources" component={this.wrappedDatasourcesView} />
|
||||||
<Route path="/segments" component={this.wrappedSegmentsView} />
|
<Route path="/segments" component={this.wrappedSegmentsView} />
|
||||||
<Route path="/tasks" component={this.wrappedTasksView} />
|
<Route path="/tasks" component={this.wrappedTasksView} />
|
||||||
<Route path="/servers" component={this.wrappedServersView} />
|
<Route path="/servers" component={this.wrappedServersView} />
|
||||||
|
|
||||||
|
<Route path="/query" component={this.wrappedQueryView} />
|
||||||
|
|
||||||
<Route path="/lookups" component={this.wrappedLookupsView} />
|
<Route path="/lookups" component={this.wrappedLookupsView} />
|
||||||
<Route component={this.wrappedHomeView} />
|
<Route component={this.wrappedHomeView} />
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type Capabilities = 'full' | 'no-sql' | 'no-proxy' | 'broken';
|
|
@ -28,7 +28,7 @@ describe('data source view', () => {
|
||||||
goToQuery={() => {}}
|
goToQuery={() => {}}
|
||||||
goToTask={() => null}
|
goToTask={() => null}
|
||||||
goToSegments={() => {}}
|
goToSegments={() => {}}
|
||||||
noSqlMode={false}
|
capabilities="full"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
expect(dataSourceView).toMatchSnapshot();
|
expect(dataSourceView).toMatchSnapshot();
|
||||||
|
|
|
@ -61,13 +61,15 @@ import {
|
||||||
QueryManager,
|
QueryManager,
|
||||||
} from '../../utils';
|
} from '../../utils';
|
||||||
import { BasicAction } from '../../utils/basic-action';
|
import { BasicAction } from '../../utils/basic-action';
|
||||||
|
import { Capabilities } from '../../utils/capabilities';
|
||||||
import { RuleUtil } from '../../utils/load-rule';
|
import { RuleUtil } from '../../utils/load-rule';
|
||||||
import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array';
|
import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array';
|
||||||
import { deepGet } from '../../utils/object-change';
|
import { deepGet } from '../../utils/object-change';
|
||||||
|
|
||||||
import './datasource-view.scss';
|
import './datasource-view.scss';
|
||||||
|
|
||||||
const tableColumns: string[] = [
|
const tableColumns: Record<Capabilities, string[]> = {
|
||||||
|
full: [
|
||||||
'Datasource',
|
'Datasource',
|
||||||
'Availability',
|
'Availability',
|
||||||
'Segment load/drop',
|
'Segment load/drop',
|
||||||
|
@ -78,8 +80,8 @@ const tableColumns: string[] = [
|
||||||
'Avg. segment size',
|
'Avg. segment size',
|
||||||
'Num rows',
|
'Num rows',
|
||||||
ACTION_COLUMN_LABEL,
|
ACTION_COLUMN_LABEL,
|
||||||
];
|
],
|
||||||
const tableColumnsNoSql: string[] = [
|
'no-sql': [
|
||||||
'Datasource',
|
'Datasource',
|
||||||
'Availability',
|
'Availability',
|
||||||
'Segment load/drop',
|
'Segment load/drop',
|
||||||
|
@ -88,7 +90,19 @@ const tableColumnsNoSql: string[] = [
|
||||||
'Compaction',
|
'Compaction',
|
||||||
'Avg. segment size',
|
'Avg. segment size',
|
||||||
ACTION_COLUMN_LABEL,
|
ACTION_COLUMN_LABEL,
|
||||||
];
|
],
|
||||||
|
'no-proxy': [
|
||||||
|
'Datasource',
|
||||||
|
'Availability',
|
||||||
|
'Segment load/drop',
|
||||||
|
'Replicated size',
|
||||||
|
'Size',
|
||||||
|
'Avg. segment size',
|
||||||
|
'Num rows',
|
||||||
|
ACTION_COLUMN_LABEL,
|
||||||
|
],
|
||||||
|
broken: ['Datasource'],
|
||||||
|
};
|
||||||
|
|
||||||
function formatLoadDrop(segmentsToLoad: number, segmentsToDrop: number): string {
|
function formatLoadDrop(segmentsToLoad: number, segmentsToDrop: number): string {
|
||||||
const loadDrop: string[] = [];
|
const loadDrop: string[] = [];
|
||||||
|
@ -133,7 +147,7 @@ export interface DatasourcesViewProps {
|
||||||
goToQuery: (initSql: string) => void;
|
goToQuery: (initSql: string) => void;
|
||||||
goToTask: (datasource?: string, openDialog?: string) => void;
|
goToTask: (datasource?: string, openDialog?: string) => void;
|
||||||
goToSegments: (datasource: string, onlyUnavailable?: boolean) => void;
|
goToSegments: (datasource: string, onlyUnavailable?: boolean) => void;
|
||||||
noSqlMode: boolean;
|
capabilities: Capabilities;
|
||||||
initDatasource?: string;
|
initDatasource?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,7 +212,7 @@ GROUP BY 1`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private datasourceQueryManager: QueryManager<
|
private datasourceQueryManager: QueryManager<
|
||||||
boolean,
|
Capabilities,
|
||||||
{ tiers: string[]; defaultRules: any[]; datasources: Datasource[] }
|
{ tiers: string[]; defaultRules: any[]; datasources: Datasource[] }
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
@ -231,9 +245,9 @@ GROUP BY 1`;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.datasourceQueryManager = new QueryManager({
|
this.datasourceQueryManager = new QueryManager({
|
||||||
processQuery: async noSqlMode => {
|
processQuery: async capabilities => {
|
||||||
let datasources: DatasourceQueryResultRow[];
|
let datasources: DatasourceQueryResultRow[];
|
||||||
if (!noSqlMode) {
|
if (capabilities !== 'no-sql') {
|
||||||
datasources = await queryDruidSql({ query: DatasourcesView.DATASOURCE_SQL });
|
datasources = await queryDruidSql({ query: DatasourcesView.DATASOURCE_SQL });
|
||||||
} else {
|
} else {
|
||||||
const datasourcesResp = await axios.get('/druid/coordinator/v1/datasources?simple');
|
const datasourcesResp = await axios.get('/druid/coordinator/v1/datasources?simple');
|
||||||
|
@ -260,6 +274,17 @@ GROUP BY 1`;
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (capabilities === 'no-proxy') {
|
||||||
|
datasources.forEach((ds: any) => {
|
||||||
|
ds.rules = [];
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
datasources,
|
||||||
|
tiers: [],
|
||||||
|
defaultRules: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const seen = countBy(datasources, (x: any) => x.datasource);
|
const seen = countBy(datasources, (x: any) => x.datasource);
|
||||||
|
|
||||||
let disabled: string[] = [];
|
let disabled: string[] = [];
|
||||||
|
@ -320,8 +345,8 @@ GROUP BY 1`;
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
const { noSqlMode } = this.props;
|
const { capabilities } = this.props;
|
||||||
this.datasourceQueryManager.runQuery(noSqlMode);
|
this.datasourceQueryManager.runQuery(capabilities);
|
||||||
window.addEventListener('resize', this.handleResize);
|
window.addEventListener('resize', this.handleResize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -468,10 +493,10 @@ GROUP BY 1`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBulkDatasourceActions() {
|
renderBulkDatasourceActions() {
|
||||||
const { goToQuery, noSqlMode } = this.props;
|
const { goToQuery, capabilities } = this.props;
|
||||||
const bulkDatasourceActionsMenu = (
|
const bulkDatasourceActionsMenu = (
|
||||||
<Menu>
|
<Menu>
|
||||||
{!noSqlMode && (
|
{capabilities !== 'no-sql' && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={IconNames.APPLICATION}
|
icon={IconNames.APPLICATION}
|
||||||
text="View SQL query for table"
|
text="View SQL query for table"
|
||||||
|
@ -581,7 +606,24 @@ GROUP BY 1`;
|
||||||
rules: any[],
|
rules: any[],
|
||||||
compactionConfig: Record<string, any>,
|
compactionConfig: Record<string, any>,
|
||||||
): BasicAction[] {
|
): BasicAction[] {
|
||||||
const { goToQuery, goToTask } = this.props;
|
const { goToQuery, goToTask, capabilities } = this.props;
|
||||||
|
|
||||||
|
const goToActions: BasicAction[] = [
|
||||||
|
{
|
||||||
|
icon: IconNames.APPLICATION,
|
||||||
|
title: 'Query with SQL',
|
||||||
|
onAction: () => goToQuery(`SELECT * FROM ${escapeSqlIdentifier(datasource)}`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: IconNames.GANTT_CHART,
|
||||||
|
title: 'Go to tasks',
|
||||||
|
onAction: () => goToTask(datasource),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (capabilities === 'no-proxy') {
|
||||||
|
return goToActions;
|
||||||
|
}
|
||||||
|
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
return [
|
return [
|
||||||
|
@ -598,12 +640,7 @@ GROUP BY 1`;
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
return [
|
return goToActions.concat([
|
||||||
{
|
|
||||||
icon: IconNames.APPLICATION,
|
|
||||||
title: 'Query with SQL',
|
|
||||||
onAction: () => goToQuery(`SELECT * FROM ${escapeSqlIdentifier(datasource)}`),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
icon: IconNames.GANTT_CHART,
|
icon: IconNames.GANTT_CHART,
|
||||||
title: 'Go to tasks',
|
title: 'Go to tasks',
|
||||||
|
@ -657,7 +694,7 @@ GROUP BY 1`;
|
||||||
intent: Intent.DANGER,
|
intent: Intent.DANGER,
|
||||||
onAction: () => this.setState({ killDatasource: datasource }),
|
onAction: () => this.setState({ killDatasource: datasource }),
|
||||||
},
|
},
|
||||||
];
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -694,7 +731,7 @@ GROUP BY 1`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderDatasourceTable() {
|
renderDatasourceTable() {
|
||||||
const { goToSegments, noSqlMode } = this.props;
|
const { goToSegments, capabilities } = this.props;
|
||||||
const {
|
const {
|
||||||
datasources,
|
datasources,
|
||||||
defaultRules,
|
defaultRules,
|
||||||
|
@ -850,7 +887,7 @@ GROUP BY 1`;
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
show: hiddenColumns.exists('Retention'),
|
show: capabilities !== 'no-proxy' && hiddenColumns.exists('Retention'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Replicated size',
|
Header: 'Replicated size',
|
||||||
|
@ -904,7 +941,7 @@ GROUP BY 1`;
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
show: hiddenColumns.exists('Compaction'),
|
show: capabilities !== 'no-proxy' && hiddenColumns.exists('Compaction'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Avg. segment size',
|
Header: 'Avg. segment size',
|
||||||
|
@ -920,7 +957,7 @@ GROUP BY 1`;
|
||||||
filterable: false,
|
filterable: false,
|
||||||
width: 100,
|
width: 100,
|
||||||
Cell: row => formatNumber(row.value),
|
Cell: row => formatNumber(row.value),
|
||||||
show: !noSqlMode && hiddenColumns.exists('Num rows'),
|
show: capabilities !== 'no-sql' && hiddenColumns.exists('Num rows'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: ACTION_COLUMN_LABEL,
|
Header: ACTION_COLUMN_LABEL,
|
||||||
|
@ -965,7 +1002,7 @@ GROUP BY 1`;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
const { noSqlMode } = this.props;
|
const { capabilities } = this.props;
|
||||||
const {
|
const {
|
||||||
showDisabled,
|
showDisabled,
|
||||||
hiddenColumns,
|
hiddenColumns,
|
||||||
|
@ -993,13 +1030,15 @@ GROUP BY 1`;
|
||||||
label="Show segment timeline"
|
label="Show segment timeline"
|
||||||
onChange={() => this.setState({ showChart: !showChart })}
|
onChange={() => this.setState({ showChart: !showChart })}
|
||||||
/>
|
/>
|
||||||
|
{capabilities !== 'no-proxy' && (
|
||||||
<Switch
|
<Switch
|
||||||
checked={showDisabled}
|
checked={showDisabled}
|
||||||
label="Show disabled"
|
label="Show disabled"
|
||||||
onChange={() => this.toggleDisabled(showDisabled)}
|
onChange={() => this.toggleDisabled(showDisabled)}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
<TableColumnSelector
|
<TableColumnSelector
|
||||||
columns={noSqlMode ? tableColumnsNoSql : tableColumns}
|
columns={tableColumns[capabilities]}
|
||||||
onChange={column =>
|
onChange={column =>
|
||||||
this.setState(prevState => ({
|
this.setState(prevState => ({
|
||||||
hiddenColumns: prevState.hiddenColumns.toggle(column),
|
hiddenColumns: prevState.hiddenColumns.toggle(column),
|
||||||
|
|
|
@ -6,17 +6,19 @@ exports[`home view matches snapshot 1`] = `
|
||||||
>
|
>
|
||||||
<StatusCard />
|
<StatusCard />
|
||||||
<DatasourcesCard
|
<DatasourcesCard
|
||||||
noSqlMode={false}
|
capabilities="full"
|
||||||
/>
|
/>
|
||||||
<SegmentsCard
|
<SegmentsCard
|
||||||
noSqlMode={false}
|
capabilities="full"
|
||||||
|
/>
|
||||||
|
<SupervisorsCard
|
||||||
|
capabilities="full"
|
||||||
/>
|
/>
|
||||||
<SupervisorsCard />
|
|
||||||
<TasksCard
|
<TasksCard
|
||||||
noSqlMode={false}
|
capabilities="full"
|
||||||
/>
|
/>
|
||||||
<ServersCard
|
<ServersCard
|
||||||
noSqlMode={false}
|
capabilities="full"
|
||||||
/>
|
/>
|
||||||
<LookupsCard />
|
<LookupsCard />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { DatasourcesCard } from './datasources-card';
|
||||||
|
|
||||||
describe('datasources card', () => {
|
describe('datasources card', () => {
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
const datasourcesCard = <DatasourcesCard noSqlMode={false} />;
|
const datasourcesCard = <DatasourcesCard capabilities="full" />;
|
||||||
|
|
||||||
const { container } = render(datasourcesCard);
|
const { container } = render(datasourcesCard);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(container.firstChild).toMatchSnapshot();
|
||||||
|
|
|
@ -21,10 +21,11 @@ import axios from 'axios';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { pluralIfNeeded, queryDruidSql, QueryManager } from '../../../utils';
|
import { pluralIfNeeded, queryDruidSql, QueryManager } from '../../../utils';
|
||||||
|
import { Capabilities } from '../../../utils/capabilities';
|
||||||
import { HomeViewCard } from '../home-view-card/home-view-card';
|
import { HomeViewCard } from '../home-view-card/home-view-card';
|
||||||
|
|
||||||
export interface DatasourcesCardProps {
|
export interface DatasourcesCardProps {
|
||||||
noSqlMode: boolean;
|
capabilities: Capabilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatasourcesCardState {
|
export interface DatasourcesCardState {
|
||||||
|
@ -37,7 +38,7 @@ export class DatasourcesCard extends React.PureComponent<
|
||||||
DatasourcesCardProps,
|
DatasourcesCardProps,
|
||||||
DatasourcesCardState
|
DatasourcesCardState
|
||||||
> {
|
> {
|
||||||
private datasourceQueryManager: QueryManager<boolean, any>;
|
private datasourceQueryManager: QueryManager<Capabilities, any>;
|
||||||
|
|
||||||
constructor(props: DatasourcesCardProps, context: any) {
|
constructor(props: DatasourcesCardProps, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
@ -47,9 +48,9 @@ export class DatasourcesCard extends React.PureComponent<
|
||||||
};
|
};
|
||||||
|
|
||||||
this.datasourceQueryManager = new QueryManager({
|
this.datasourceQueryManager = new QueryManager({
|
||||||
processQuery: async noSqlMode => {
|
processQuery: async capabilities => {
|
||||||
let datasources: string[];
|
let datasources: string[];
|
||||||
if (!noSqlMode) {
|
if (capabilities !== 'no-sql') {
|
||||||
datasources = await queryDruidSql({
|
datasources = await queryDruidSql({
|
||||||
query: `SELECT datasource FROM sys.segments GROUP BY 1`,
|
query: `SELECT datasource FROM sys.segments GROUP BY 1`,
|
||||||
});
|
});
|
||||||
|
@ -70,9 +71,9 @@ export class DatasourcesCard extends React.PureComponent<
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
const { noSqlMode } = this.props;
|
const { capabilities } = this.props;
|
||||||
|
|
||||||
this.datasourceQueryManager.runQuery(noSqlMode);
|
this.datasourceQueryManager.runQuery(capabilities);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
componentWillUnmount(): void {
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { HomeView } from './home-view';
|
||||||
|
|
||||||
describe('home view', () => {
|
describe('home view', () => {
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
const homeView = shallow(<HomeView noSqlMode={false} />);
|
const homeView = shallow(<HomeView capabilities="full" />);
|
||||||
expect(homeView).toMatchSnapshot();
|
expect(homeView).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Capabilities } from '../../utils/capabilities';
|
||||||
|
|
||||||
import { DatasourcesCard } from './datasources-card/datasources-card';
|
import { DatasourcesCard } from './datasources-card/datasources-card';
|
||||||
import { LookupsCard } from './lookups-card/lookups-card';
|
import { LookupsCard } from './lookups-card/lookups-card';
|
||||||
import { SegmentsCard } from './segments-card/segments-card';
|
import { SegmentsCard } from './segments-card/segments-card';
|
||||||
|
@ -29,20 +31,20 @@ import { TasksCard } from './tasks-card/tasks-card';
|
||||||
import './home-view.scss';
|
import './home-view.scss';
|
||||||
|
|
||||||
export interface HomeViewProps {
|
export interface HomeViewProps {
|
||||||
noSqlMode: boolean;
|
capabilities: Capabilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HomeView = React.memo(function HomeView(props: HomeViewProps) {
|
export const HomeView = React.memo(function HomeView(props: HomeViewProps) {
|
||||||
const { noSqlMode } = props;
|
const { capabilities } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="home-view app-view">
|
<div className="home-view app-view">
|
||||||
<StatusCard />
|
<StatusCard />
|
||||||
<DatasourcesCard noSqlMode={noSqlMode} />
|
<DatasourcesCard capabilities={capabilities} />
|
||||||
<SegmentsCard noSqlMode={noSqlMode} />
|
<SegmentsCard capabilities={capabilities} />
|
||||||
<SupervisorsCard />
|
<SupervisorsCard capabilities={capabilities} />
|
||||||
<TasksCard noSqlMode={noSqlMode} />
|
<TasksCard capabilities={capabilities} />
|
||||||
<ServersCard noSqlMode={noSqlMode} />
|
<ServersCard capabilities={capabilities} />
|
||||||
<LookupsCard />
|
<LookupsCard />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { SegmentsCard } from './segments-card';
|
||||||
|
|
||||||
describe('segments card', () => {
|
describe('segments card', () => {
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
const segmentsCard = <SegmentsCard noSqlMode={false} />;
|
const segmentsCard = <SegmentsCard capabilities="full" />;
|
||||||
|
|
||||||
const { container } = render(segmentsCard);
|
const { container } = render(segmentsCard);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(container.firstChild).toMatchSnapshot();
|
||||||
|
|
|
@ -22,11 +22,12 @@ import { sum } from 'd3-array';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { pluralIfNeeded, queryDruidSql, QueryManager } from '../../../utils';
|
import { pluralIfNeeded, queryDruidSql, QueryManager } from '../../../utils';
|
||||||
|
import { Capabilities } from '../../../utils/capabilities';
|
||||||
import { deepGet } from '../../../utils/object-change';
|
import { deepGet } from '../../../utils/object-change';
|
||||||
import { HomeViewCard } from '../home-view-card/home-view-card';
|
import { HomeViewCard } from '../home-view-card/home-view-card';
|
||||||
|
|
||||||
export interface SegmentsCardProps {
|
export interface SegmentsCardProps {
|
||||||
noSqlMode: boolean;
|
capabilities: Capabilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SegmentsCardState {
|
export interface SegmentsCardState {
|
||||||
|
@ -37,7 +38,7 @@ export interface SegmentsCardState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SegmentsCard extends React.PureComponent<SegmentsCardProps, SegmentsCardState> {
|
export class SegmentsCard extends React.PureComponent<SegmentsCardProps, SegmentsCardState> {
|
||||||
private segmentQueryManager: QueryManager<boolean, any>;
|
private segmentQueryManager: QueryManager<Capabilities, any>;
|
||||||
|
|
||||||
constructor(props: SegmentsCardProps, context: any) {
|
constructor(props: SegmentsCardProps, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
@ -48,8 +49,8 @@ export class SegmentsCard extends React.PureComponent<SegmentsCardProps, Segment
|
||||||
};
|
};
|
||||||
|
|
||||||
this.segmentQueryManager = new QueryManager({
|
this.segmentQueryManager = new QueryManager({
|
||||||
processQuery: async noSqlMode => {
|
processQuery: async capabilities => {
|
||||||
if (noSqlMode) {
|
if (capabilities === 'no-sql') {
|
||||||
const loadstatusResp = await axios.get('/druid/coordinator/v1/loadstatus?simple');
|
const loadstatusResp = await axios.get('/druid/coordinator/v1/loadstatus?simple');
|
||||||
const loadstatus = loadstatusResp.data;
|
const loadstatus = loadstatusResp.data;
|
||||||
const unavailableSegmentNum = sum(Object.keys(loadstatus), key => loadstatus[key]);
|
const unavailableSegmentNum = sum(Object.keys(loadstatus), key => loadstatus[key]);
|
||||||
|
@ -86,9 +87,9 @@ FROM sys.segments`,
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
const { noSqlMode } = this.props;
|
const { capabilities } = this.props;
|
||||||
|
|
||||||
this.segmentQueryManager.runQuery(noSqlMode);
|
this.segmentQueryManager.runQuery(capabilities);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
componentWillUnmount(): void {
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { ServersCard } from './servers-card';
|
||||||
|
|
||||||
describe('servers card', () => {
|
describe('servers card', () => {
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
const serversCard = <ServersCard noSqlMode={false} />;
|
const serversCard = <ServersCard capabilities="full" />;
|
||||||
|
|
||||||
const { container } = render(serversCard);
|
const { container } = render(serversCard);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(container.firstChild).toMatchSnapshot();
|
||||||
|
|
|
@ -21,10 +21,11 @@ import axios from 'axios';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { compact, lookupBy, pluralIfNeeded, queryDruidSql, QueryManager } from '../../../utils';
|
import { compact, lookupBy, pluralIfNeeded, queryDruidSql, QueryManager } from '../../../utils';
|
||||||
|
import { Capabilities } from '../../../utils/capabilities';
|
||||||
import { HomeViewCard } from '../home-view-card/home-view-card';
|
import { HomeViewCard } from '../home-view-card/home-view-card';
|
||||||
|
|
||||||
export interface ServersCardProps {
|
export interface ServersCardProps {
|
||||||
noSqlMode: boolean;
|
capabilities: Capabilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServersCardState {
|
export interface ServersCardState {
|
||||||
|
@ -55,7 +56,7 @@ export class ServersCard extends React.PureComponent<ServersCardProps, ServersCa
|
||||||
return <p>{text}</p>;
|
return <p>{text}</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
private serverQueryManager: QueryManager<boolean, any>;
|
private serverQueryManager: QueryManager<Capabilities, any>;
|
||||||
|
|
||||||
constructor(props: ServersCardProps, context: any) {
|
constructor(props: ServersCardProps, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
@ -72,8 +73,8 @@ export class ServersCard extends React.PureComponent<ServersCardProps, ServersCa
|
||||||
};
|
};
|
||||||
|
|
||||||
this.serverQueryManager = new QueryManager({
|
this.serverQueryManager = new QueryManager({
|
||||||
processQuery: async noSqlMode => {
|
processQuery: async capabilities => {
|
||||||
if (noSqlMode) {
|
if (capabilities === 'no-sql') {
|
||||||
const serversResp = await axios.get('/druid/coordinator/v1/servers?simple');
|
const serversResp = await axios.get('/druid/coordinator/v1/servers?simple');
|
||||||
const middleManagerResp = await axios.get('/druid/indexer/v1/workers');
|
const middleManagerResp = await axios.get('/druid/indexer/v1/workers');
|
||||||
return {
|
return {
|
||||||
|
@ -109,9 +110,9 @@ export class ServersCard extends React.PureComponent<ServersCardProps, ServersCa
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
const { noSqlMode } = this.props;
|
const { capabilities } = this.props;
|
||||||
|
|
||||||
this.serverQueryManager.runQuery(noSqlMode);
|
this.serverQueryManager.runQuery(capabilities);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
componentWillUnmount(): void {
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { SupervisorsCard } from './supervisors-card';
|
||||||
|
|
||||||
describe('supervisors card', () => {
|
describe('supervisors card', () => {
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
const supervisorsCard = <SupervisorsCard />;
|
const supervisorsCard = <SupervisorsCard capabilities="full" />;
|
||||||
|
|
||||||
const { container } = render(supervisorsCard);
|
const { container } = render(supervisorsCard);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(container.firstChild).toMatchSnapshot();
|
||||||
|
|
|
@ -20,10 +20,13 @@ import { IconNames } from '@blueprintjs/icons';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { pluralIfNeeded, QueryManager } from '../../../utils';
|
import { pluralIfNeeded, queryDruidSql, QueryManager } from '../../../utils';
|
||||||
|
import { Capabilities } from '../../../utils/capabilities';
|
||||||
import { HomeViewCard } from '../home-view-card/home-view-card';
|
import { HomeViewCard } from '../home-view-card/home-view-card';
|
||||||
|
|
||||||
export interface SupervisorsCardProps {}
|
export interface SupervisorsCardProps {
|
||||||
|
capabilities: Capabilities;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SupervisorsCardState {
|
export interface SupervisorsCardState {
|
||||||
supervisorCountLoading: boolean;
|
supervisorCountLoading: boolean;
|
||||||
|
@ -36,7 +39,7 @@ export class SupervisorsCard extends React.PureComponent<
|
||||||
SupervisorsCardProps,
|
SupervisorsCardProps,
|
||||||
SupervisorsCardState
|
SupervisorsCardState
|
||||||
> {
|
> {
|
||||||
private supervisorQueryManager: QueryManager<null, any>;
|
private supervisorQueryManager: QueryManager<Capabilities, any>;
|
||||||
|
|
||||||
constructor(props: SupervisorsCardProps, context: any) {
|
constructor(props: SupervisorsCardProps, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
@ -47,15 +50,25 @@ export class SupervisorsCard extends React.PureComponent<
|
||||||
};
|
};
|
||||||
|
|
||||||
this.supervisorQueryManager = new QueryManager({
|
this.supervisorQueryManager = new QueryManager({
|
||||||
processQuery: async () => {
|
processQuery: async capabilities => {
|
||||||
|
if (capabilities !== 'no-sql') {
|
||||||
|
return (await queryDruidSql({
|
||||||
|
query: `SELECT
|
||||||
|
COUNT(*) FILTER (WHERE "suspended" = 0) AS "runningSupervisorCount",
|
||||||
|
COUNT(*) FILTER (WHERE "suspended" = 1) AS "suspendedSupervisorCount"
|
||||||
|
FROM sys.supervisors`,
|
||||||
|
}))[0];
|
||||||
|
} else {
|
||||||
const resp = await axios.get('/druid/indexer/v1/supervisor?full');
|
const resp = await axios.get('/druid/indexer/v1/supervisor?full');
|
||||||
const data = resp.data;
|
const data = resp.data;
|
||||||
const runningSupervisorCount = data.filter((d: any) => d.spec.suspended === false).length;
|
const runningSupervisorCount = data.filter((d: any) => d.spec.suspended === false).length;
|
||||||
const suspendedSupervisorCount = data.filter((d: any) => d.spec.suspended === true).length;
|
const suspendedSupervisorCount = data.filter((d: any) => d.spec.suspended === true)
|
||||||
|
.length;
|
||||||
return {
|
return {
|
||||||
runningSupervisorCount,
|
runningSupervisorCount,
|
||||||
suspendedSupervisorCount,
|
suspendedSupervisorCount,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onStateChange: ({ result, loading, error }) => {
|
onStateChange: ({ result, loading, error }) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -69,7 +82,9 @@ export class SupervisorsCard extends React.PureComponent<
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
this.supervisorQueryManager.runQuery(null);
|
const { capabilities } = this.props;
|
||||||
|
|
||||||
|
this.supervisorQueryManager.runQuery(capabilities);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
componentWillUnmount(): void {
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { TasksCard } from './tasks-card';
|
||||||
|
|
||||||
describe('tasks card', () => {
|
describe('tasks card', () => {
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
const tasksCard = <TasksCard noSqlMode={false} />;
|
const tasksCard = <TasksCard capabilities="full" />;
|
||||||
|
|
||||||
const { container } = render(tasksCard);
|
const { container } = render(tasksCard);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(container.firstChild).toMatchSnapshot();
|
||||||
|
|
|
@ -21,10 +21,11 @@ import axios from 'axios';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { lookupBy, pluralIfNeeded, queryDruidSql, QueryManager } from '../../../utils';
|
import { lookupBy, pluralIfNeeded, queryDruidSql, QueryManager } from '../../../utils';
|
||||||
|
import { Capabilities } from '../../../utils/capabilities';
|
||||||
import { HomeViewCard } from '../home-view-card/home-view-card';
|
import { HomeViewCard } from '../home-view-card/home-view-card';
|
||||||
|
|
||||||
export interface TasksCardProps {
|
export interface TasksCardProps {
|
||||||
noSqlMode: boolean;
|
capabilities: Capabilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TasksCardState {
|
export interface TasksCardState {
|
||||||
|
@ -38,7 +39,7 @@ export interface TasksCardState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TasksCard extends React.PureComponent<TasksCardProps, TasksCardState> {
|
export class TasksCard extends React.PureComponent<TasksCardProps, TasksCardState> {
|
||||||
private taskQueryManager: QueryManager<boolean, any>;
|
private taskQueryManager: QueryManager<Capabilities, any>;
|
||||||
|
|
||||||
constructor(props: TasksCardProps, context: any) {
|
constructor(props: TasksCardProps, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
@ -52,8 +53,8 @@ export class TasksCard extends React.PureComponent<TasksCardProps, TasksCardStat
|
||||||
};
|
};
|
||||||
|
|
||||||
this.taskQueryManager = new QueryManager({
|
this.taskQueryManager = new QueryManager({
|
||||||
processQuery: async noSqlMode => {
|
processQuery: async capabilities => {
|
||||||
if (noSqlMode) {
|
if (capabilities === 'no-sql') {
|
||||||
const completeTasksResp = await axios.get('/druid/indexer/v1/completeTasks');
|
const completeTasksResp = await axios.get('/druid/indexer/v1/completeTasks');
|
||||||
const runningTasksResp = await axios.get('/druid/indexer/v1/runningTasks');
|
const runningTasksResp = await axios.get('/druid/indexer/v1/runningTasks');
|
||||||
const pendingTasksResp = await axios.get('/druid/indexer/v1/pendingTasks');
|
const pendingTasksResp = await axios.get('/druid/indexer/v1/pendingTasks');
|
||||||
|
@ -91,9 +92,9 @@ GROUP BY 1`,
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
const { noSqlMode } = this.props;
|
const { capabilities } = this.props;
|
||||||
|
|
||||||
this.taskQueryManager.runQuery(noSqlMode);
|
this.taskQueryManager.runQuery(capabilities);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
componentWillUnmount(): void {
|
||||||
|
|
|
@ -28,7 +28,7 @@ describe('segments-view', () => {
|
||||||
datasource={'test'}
|
datasource={'test'}
|
||||||
onlyUnavailable={false}
|
onlyUnavailable={false}
|
||||||
goToQuery={() => {}}
|
goToQuery={() => {}}
|
||||||
noSqlMode={false}
|
capabilities="full"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
expect(segmentsView).toMatchSnapshot();
|
expect(segmentsView).toMatchSnapshot();
|
||||||
|
|
|
@ -56,11 +56,13 @@ import {
|
||||||
sqlQueryCustomTableFilter,
|
sqlQueryCustomTableFilter,
|
||||||
} from '../../utils';
|
} from '../../utils';
|
||||||
import { BasicAction } from '../../utils/basic-action';
|
import { BasicAction } from '../../utils/basic-action';
|
||||||
|
import { Capabilities } from '../../utils/capabilities';
|
||||||
import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array';
|
import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array';
|
||||||
|
|
||||||
import './segments-view.scss';
|
import './segments-view.scss';
|
||||||
|
|
||||||
const tableColumns: string[] = [
|
const tableColumns: Record<Capabilities, string[]> = {
|
||||||
|
full: [
|
||||||
'Segment ID',
|
'Segment ID',
|
||||||
'Datasource',
|
'Datasource',
|
||||||
'Start',
|
'Start',
|
||||||
|
@ -75,8 +77,8 @@ const tableColumns: string[] = [
|
||||||
'Is available',
|
'Is available',
|
||||||
'Is overshadowed',
|
'Is overshadowed',
|
||||||
ACTION_COLUMN_LABEL,
|
ACTION_COLUMN_LABEL,
|
||||||
];
|
],
|
||||||
const tableColumnsNoSql: string[] = [
|
'no-sql': [
|
||||||
'Segment ID',
|
'Segment ID',
|
||||||
'Datasource',
|
'Datasource',
|
||||||
'Start',
|
'Start',
|
||||||
|
@ -84,13 +86,31 @@ const tableColumnsNoSql: string[] = [
|
||||||
'Version',
|
'Version',
|
||||||
'Partition',
|
'Partition',
|
||||||
'Size',
|
'Size',
|
||||||
];
|
ACTION_COLUMN_LABEL,
|
||||||
|
],
|
||||||
|
'no-proxy': [
|
||||||
|
'Segment ID',
|
||||||
|
'Datasource',
|
||||||
|
'Start',
|
||||||
|
'End',
|
||||||
|
'Version',
|
||||||
|
'Partition',
|
||||||
|
'Size',
|
||||||
|
'Num rows',
|
||||||
|
'Replicas',
|
||||||
|
'Is published',
|
||||||
|
'Is realtime',
|
||||||
|
'Is available',
|
||||||
|
'Is overshadowed',
|
||||||
|
],
|
||||||
|
broken: ['Segment ID'],
|
||||||
|
};
|
||||||
|
|
||||||
export interface SegmentsViewProps {
|
export interface SegmentsViewProps {
|
||||||
goToQuery: (initSql: string) => void;
|
goToQuery: (initSql: string) => void;
|
||||||
datasource: string | undefined;
|
datasource: string | undefined;
|
||||||
onlyUnavailable: boolean | undefined;
|
onlyUnavailable: boolean | undefined;
|
||||||
noSqlMode: boolean;
|
capabilities: Capabilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SegmentsViewState {
|
export interface SegmentsViewState {
|
||||||
|
@ -318,8 +338,8 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
const { noSqlMode } = this.props;
|
const { capabilities } = this.props;
|
||||||
if (noSqlMode) {
|
if (capabilities === 'no-sql') {
|
||||||
this.segmentsNoSqlQueryManager.runQuery(null);
|
this.segmentsNoSqlQueryManager.runQuery(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -389,7 +409,7 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
|
||||||
hiddenColumns,
|
hiddenColumns,
|
||||||
groupByInterval,
|
groupByInterval,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { noSqlMode } = this.props;
|
const { capabilities } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactTable
|
<ReactTable
|
||||||
|
@ -407,7 +427,7 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
|
||||||
this.setState({ segmentFilter: filtered });
|
this.setState({ segmentFilter: filtered });
|
||||||
}}
|
}}
|
||||||
onFetchData={
|
onFetchData={
|
||||||
noSqlMode
|
capabilities === 'no-sql'
|
||||||
? this.fetchClientSideData
|
? this.fetchClientSideData
|
||||||
: state => {
|
: state => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -536,7 +556,7 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
|
||||||
filterable: false,
|
filterable: false,
|
||||||
defaultSortDesc: true,
|
defaultSortDesc: true,
|
||||||
Cell: row => (row.original.is_available ? formatNumber(row.value) : <em>(unknown)</em>),
|
Cell: row => (row.original.is_available ? formatNumber(row.value) : <em>(unknown)</em>),
|
||||||
show: !noSqlMode && hiddenColumns.exists('Num rows'),
|
show: capabilities !== 'no-sql' && hiddenColumns.exists('Num rows'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Replicas',
|
Header: 'Replicas',
|
||||||
|
@ -544,35 +564,35 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
|
||||||
width: 60,
|
width: 60,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
defaultSortDesc: true,
|
defaultSortDesc: true,
|
||||||
show: !noSqlMode && hiddenColumns.exists('Replicas'),
|
show: capabilities !== 'no-sql' && hiddenColumns.exists('Replicas'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Is published',
|
Header: 'Is published',
|
||||||
id: 'is_published',
|
id: 'is_published',
|
||||||
accessor: row => String(Boolean(row.is_published)),
|
accessor: row => String(Boolean(row.is_published)),
|
||||||
Filter: makeBooleanFilter(),
|
Filter: makeBooleanFilter(),
|
||||||
show: !noSqlMode && hiddenColumns.exists('Is published'),
|
show: capabilities !== 'no-sql' && hiddenColumns.exists('Is published'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Is realtime',
|
Header: 'Is realtime',
|
||||||
id: 'is_realtime',
|
id: 'is_realtime',
|
||||||
accessor: row => String(Boolean(row.is_realtime)),
|
accessor: row => String(Boolean(row.is_realtime)),
|
||||||
Filter: makeBooleanFilter(),
|
Filter: makeBooleanFilter(),
|
||||||
show: !noSqlMode && hiddenColumns.exists('Is realtime'),
|
show: capabilities !== 'no-sql' && hiddenColumns.exists('Is realtime'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Is available',
|
Header: 'Is available',
|
||||||
id: 'is_available',
|
id: 'is_available',
|
||||||
accessor: row => String(Boolean(row.is_available)),
|
accessor: row => String(Boolean(row.is_available)),
|
||||||
Filter: makeBooleanFilter(),
|
Filter: makeBooleanFilter(),
|
||||||
show: !noSqlMode && hiddenColumns.exists('Is available'),
|
show: capabilities !== 'no-sql' && hiddenColumns.exists('Is available'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Is overshadowed',
|
Header: 'Is overshadowed',
|
||||||
id: 'is_overshadowed',
|
id: 'is_overshadowed',
|
||||||
accessor: row => String(Boolean(row.is_overshadowed)),
|
accessor: row => String(Boolean(row.is_overshadowed)),
|
||||||
Filter: makeBooleanFilter(),
|
Filter: makeBooleanFilter(),
|
||||||
show: !noSqlMode && hiddenColumns.exists('Is overshadowed'),
|
show: capabilities !== 'no-sql' && hiddenColumns.exists('Is overshadowed'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: ACTION_COLUMN_LABEL,
|
Header: ACTION_COLUMN_LABEL,
|
||||||
|
@ -598,7 +618,7 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
Aggregated: () => '',
|
Aggregated: () => '',
|
||||||
show: hiddenColumns.exists(ACTION_COLUMN_LABEL),
|
show: capabilities !== 'no-proxy' && hiddenColumns.exists(ACTION_COLUMN_LABEL),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
defaultPageSize={SegmentsView.PAGE_SIZE}
|
defaultPageSize={SegmentsView.PAGE_SIZE}
|
||||||
|
@ -638,12 +658,12 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBulkSegmentsActions() {
|
renderBulkSegmentsActions() {
|
||||||
const { goToQuery, noSqlMode } = this.props;
|
const { goToQuery, capabilities } = this.props;
|
||||||
const lastSegmentsQuery = this.segmentsSqlQueryManager.getLastIntermediateQuery();
|
const lastSegmentsQuery = this.segmentsSqlQueryManager.getLastIntermediateQuery();
|
||||||
|
|
||||||
const bulkSegmentsActionsMenu = (
|
const bulkSegmentsActionsMenu = (
|
||||||
<Menu>
|
<Menu>
|
||||||
{!noSqlMode && (
|
{capabilities !== 'no-sql' && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={IconNames.APPLICATION}
|
icon={IconNames.APPLICATION}
|
||||||
text="View SQL query for table"
|
text="View SQL query for table"
|
||||||
|
@ -673,7 +693,7 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
|
||||||
actions,
|
actions,
|
||||||
hiddenColumns,
|
hiddenColumns,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { noSqlMode } = this.props;
|
const { capabilities } = this.props;
|
||||||
const { groupByInterval } = this.state;
|
const { groupByInterval } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -682,7 +702,7 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
|
||||||
<ViewControlBar label="Segments">
|
<ViewControlBar label="Segments">
|
||||||
<RefreshButton
|
<RefreshButton
|
||||||
onRefresh={auto =>
|
onRefresh={auto =>
|
||||||
noSqlMode
|
capabilities
|
||||||
? this.segmentsNoSqlQueryManager.rerunLastQuery(auto)
|
? this.segmentsNoSqlQueryManager.rerunLastQuery(auto)
|
||||||
: this.segmentsSqlQueryManager.rerunLastQuery(auto)
|
: this.segmentsSqlQueryManager.rerunLastQuery(auto)
|
||||||
}
|
}
|
||||||
|
@ -694,7 +714,7 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
|
||||||
active={!groupByInterval}
|
active={!groupByInterval}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
this.setState({ groupByInterval: false });
|
this.setState({ groupByInterval: false });
|
||||||
noSqlMode ? this.fetchClientSideData() : this.fetchData(false);
|
capabilities === 'no-sql' ? this.fetchClientSideData() : this.fetchData(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
None
|
None
|
||||||
|
@ -711,7 +731,7 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
{this.renderBulkSegmentsActions()}
|
{this.renderBulkSegmentsActions()}
|
||||||
<TableColumnSelector
|
<TableColumnSelector
|
||||||
columns={noSqlMode ? tableColumnsNoSql : tableColumns}
|
columns={tableColumns[capabilities]}
|
||||||
onChange={column =>
|
onChange={column =>
|
||||||
this.setState(prevState => ({
|
this.setState(prevState => ({
|
||||||
hiddenColumns: prevState.hiddenColumns.toggle(column),
|
hiddenColumns: prevState.hiddenColumns.toggle(column),
|
||||||
|
|
|
@ -28,7 +28,7 @@ describe('servers view', () => {
|
||||||
middleManager={'test'}
|
middleManager={'test'}
|
||||||
goToQuery={() => {}}
|
goToQuery={() => {}}
|
||||||
goToTask={() => {}}
|
goToTask={() => {}}
|
||||||
noSqlMode={false}
|
capabilities="full"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
expect(serversView).toMatchSnapshot();
|
expect(serversView).toMatchSnapshot();
|
||||||
|
|
|
@ -53,12 +53,13 @@ import {
|
||||||
QueryManager,
|
QueryManager,
|
||||||
} from '../../utils';
|
} from '../../utils';
|
||||||
import { BasicAction } from '../../utils/basic-action';
|
import { BasicAction } from '../../utils/basic-action';
|
||||||
|
import { Capabilities } from '../../utils/capabilities';
|
||||||
import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array';
|
import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array';
|
||||||
import { deepGet } from '../../utils/object-change';
|
import { deepGet } from '../../utils/object-change';
|
||||||
|
|
||||||
import './servers-view.scss';
|
import './servers-view.scss';
|
||||||
|
|
||||||
const serverTableColumns: string[] = [
|
const allColumns: string[] = [
|
||||||
'Server',
|
'Server',
|
||||||
'Type',
|
'Type',
|
||||||
'Tier',
|
'Tier',
|
||||||
|
@ -71,6 +72,13 @@ const serverTableColumns: string[] = [
|
||||||
ACTION_COLUMN_LABEL,
|
ACTION_COLUMN_LABEL,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const tableColumns: Record<Capabilities, string[]> = {
|
||||||
|
full: allColumns,
|
||||||
|
'no-sql': allColumns,
|
||||||
|
'no-proxy': ['Server', 'Type', 'Tier', 'Host', 'Port', 'Curr size', 'Max size', 'Usage'],
|
||||||
|
broken: ['Server'],
|
||||||
|
};
|
||||||
|
|
||||||
function formatQueues(
|
function formatQueues(
|
||||||
segmentsToLoad: number,
|
segmentsToLoad: number,
|
||||||
segmentsToLoadSize: number,
|
segmentsToLoadSize: number,
|
||||||
|
@ -95,7 +103,7 @@ export interface ServersViewProps {
|
||||||
middleManager: string | undefined;
|
middleManager: string | undefined;
|
||||||
goToQuery: (initSql: string) => void;
|
goToQuery: (initSql: string) => void;
|
||||||
goToTask: (taskId: string) => void;
|
goToTask: (taskId: string) => void;
|
||||||
noSqlMode: boolean;
|
capabilities: Capabilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServersViewState {
|
export interface ServersViewState {
|
||||||
|
@ -150,7 +158,7 @@ interface ServerResultRow
|
||||||
Partial<MiddleManagerQueryResultRow> {}
|
Partial<MiddleManagerQueryResultRow> {}
|
||||||
|
|
||||||
export class ServersView extends React.PureComponent<ServersViewProps, ServersViewState> {
|
export class ServersView extends React.PureComponent<ServersViewProps, ServersViewState> {
|
||||||
private serverQueryManager: QueryManager<boolean, ServerResultRow[]>;
|
private serverQueryManager: QueryManager<Capabilities, ServerResultRow[]>;
|
||||||
|
|
||||||
// Ranking
|
// Ranking
|
||||||
// coordinator => 7
|
// coordinator => 7
|
||||||
|
@ -207,14 +215,18 @@ ORDER BY "rank" DESC, "server" DESC`;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.serverQueryManager = new QueryManager({
|
this.serverQueryManager = new QueryManager({
|
||||||
processQuery: async noSqlMode => {
|
processQuery: async capabilities => {
|
||||||
let servers: ServerQueryResultRow[];
|
let servers: ServerQueryResultRow[];
|
||||||
if (!noSqlMode) {
|
if (capabilities !== 'no-sql') {
|
||||||
servers = await queryDruidSql({ query: ServersView.SERVER_SQL });
|
servers = await queryDruidSql({ query: ServersView.SERVER_SQL });
|
||||||
} else {
|
} else {
|
||||||
servers = await ServersView.getServers();
|
servers = await ServersView.getServers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (capabilities === 'no-proxy') {
|
||||||
|
return servers;
|
||||||
|
}
|
||||||
|
|
||||||
const loadQueueResponse = await axios.get('/druid/coordinator/v1/loadqueue?simple');
|
const loadQueueResponse = await axios.get('/druid/coordinator/v1/loadqueue?simple');
|
||||||
const loadQueues: Record<string, LoadQueueStatus> = loadQueueResponse.data;
|
const loadQueues: Record<string, LoadQueueStatus> = loadQueueResponse.data;
|
||||||
servers = servers.map((s: any) => {
|
servers = servers.map((s: any) => {
|
||||||
|
@ -264,8 +276,8 @@ ORDER BY "rank" DESC, "server" DESC`;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
const { noSqlMode } = this.props;
|
const { capabilities } = this.props;
|
||||||
this.serverQueryManager.runQuery(noSqlMode);
|
this.serverQueryManager.runQuery(capabilities);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
componentWillUnmount(): void {
|
||||||
|
@ -273,6 +285,7 @@ ORDER BY "rank" DESC, "server" DESC`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderServersTable() {
|
renderServersTable() {
|
||||||
|
const { capabilities } = this.props;
|
||||||
const {
|
const {
|
||||||
servers,
|
servers,
|
||||||
serversLoading,
|
serversLoading,
|
||||||
|
@ -528,7 +541,7 @@ ORDER BY "rank" DESC, "server" DESC`;
|
||||||
segmentsToDropSize,
|
segmentsToDropSize,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
show: hiddenColumns.exists('Detail'),
|
show: capabilities !== 'no-proxy' && hiddenColumns.exists('Detail'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: ACTION_COLUMN_LABEL,
|
Header: ACTION_COLUMN_LABEL,
|
||||||
|
@ -542,7 +555,7 @@ ORDER BY "rank" DESC, "server" DESC`;
|
||||||
const workerActions = this.getWorkerActions(row.value.host, disabled);
|
const workerActions = this.getWorkerActions(row.value.host, disabled);
|
||||||
return <ActionCell actions={workerActions} />;
|
return <ActionCell actions={workerActions} />;
|
||||||
},
|
},
|
||||||
show: hiddenColumns.exists(ACTION_COLUMN_LABEL),
|
show: capabilities !== 'no-proxy' && hiddenColumns.exists(ACTION_COLUMN_LABEL),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
@ -628,11 +641,11 @@ ORDER BY "rank" DESC, "server" DESC`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBulkServersActions() {
|
renderBulkServersActions() {
|
||||||
const { goToQuery, noSqlMode } = this.props;
|
const { goToQuery, capabilities } = this.props;
|
||||||
|
|
||||||
const bulkserversActionsMenu = (
|
const bulkserversActionsMenu = (
|
||||||
<Menu>
|
<Menu>
|
||||||
{!noSqlMode && (
|
{capabilities !== 'no-sql' && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={IconNames.APPLICATION}
|
icon={IconNames.APPLICATION}
|
||||||
text="View SQL query for table"
|
text="View SQL query for table"
|
||||||
|
@ -652,6 +665,7 @@ ORDER BY "rank" DESC, "server" DESC`;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
|
const { capabilities } = this.props;
|
||||||
const { groupServersBy, hiddenColumns } = this.state;
|
const { groupServersBy, hiddenColumns } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -684,7 +698,7 @@ ORDER BY "rank" DESC, "server" DESC`;
|
||||||
/>
|
/>
|
||||||
{this.renderBulkServersActions()}
|
{this.renderBulkServersActions()}
|
||||||
<TableColumnSelector
|
<TableColumnSelector
|
||||||
columns={serverTableColumns}
|
columns={tableColumns[capabilities]}
|
||||||
onChange={column =>
|
onChange={column =>
|
||||||
this.setState(prevState => ({
|
this.setState(prevState => ({
|
||||||
hiddenColumns: prevState.hiddenColumns.toggle(column),
|
hiddenColumns: prevState.hiddenColumns.toggle(column),
|
||||||
|
|
|
@ -31,12 +31,12 @@ exports[`tasks view matches snapshot 1`] = `
|
||||||
<Blueprint3.Menu>
|
<Blueprint3.Menu>
|
||||||
<Blueprint3.MenuItem
|
<Blueprint3.MenuItem
|
||||||
disabled={false}
|
disabled={false}
|
||||||
icon="cloud-upload"
|
icon="application"
|
||||||
multiline={false}
|
multiline={false}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
popoverProps={Object {}}
|
popoverProps={Object {}}
|
||||||
shouldDismissPopover={true}
|
shouldDismissPopover={true}
|
||||||
text="Go to data loader"
|
text="View SQL query for table"
|
||||||
/>
|
/>
|
||||||
<Blueprint3.MenuItem
|
<Blueprint3.MenuItem
|
||||||
disabled={false}
|
disabled={false}
|
||||||
|
@ -47,35 +47,6 @@ exports[`tasks view matches snapshot 1`] = `
|
||||||
shouldDismissPopover={true}
|
shouldDismissPopover={true}
|
||||||
text="Submit JSON supervisor"
|
text="Submit JSON supervisor"
|
||||||
/>
|
/>
|
||||||
</Blueprint3.Menu>
|
|
||||||
}
|
|
||||||
defaultIsOpen={false}
|
|
||||||
disabled={false}
|
|
||||||
fill={false}
|
|
||||||
hasBackdrop={false}
|
|
||||||
hoverCloseDelay={300}
|
|
||||||
hoverOpenDelay={150}
|
|
||||||
inheritDarkTheme={true}
|
|
||||||
interactionKind="click"
|
|
||||||
minimal={false}
|
|
||||||
modifiers={Object {}}
|
|
||||||
openOnTargetFocus={true}
|
|
||||||
position="bottom-left"
|
|
||||||
targetTagName="span"
|
|
||||||
transitionDuration={300}
|
|
||||||
usePortal={true}
|
|
||||||
wrapperTagName="span"
|
|
||||||
>
|
|
||||||
<Blueprint3.Button
|
|
||||||
icon="plus"
|
|
||||||
text="Submit supervisor"
|
|
||||||
/>
|
|
||||||
</Blueprint3.Popover>
|
|
||||||
<Blueprint3.Popover
|
|
||||||
boundary="scrollParent"
|
|
||||||
captureDismiss={false}
|
|
||||||
content={
|
|
||||||
<Blueprint3.Menu>
|
|
||||||
<Blueprint3.MenuItem
|
<Blueprint3.MenuItem
|
||||||
disabled={false}
|
disabled={false}
|
||||||
icon="play"
|
icon="play"
|
||||||
|
@ -200,7 +171,7 @@ exports[`tasks view matches snapshot 1`] = `
|
||||||
Array [
|
Array [
|
||||||
Object {
|
Object {
|
||||||
"Header": "Datasource",
|
"Header": "Datasource",
|
||||||
"accessor": "id",
|
"accessor": "supervisor_id",
|
||||||
"id": "datasource",
|
"id": "datasource",
|
||||||
"show": true,
|
"show": true,
|
||||||
"width": 300,
|
"width": 300,
|
||||||
|
@ -214,7 +185,7 @@ exports[`tasks view matches snapshot 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"Header": "Topic/Stream",
|
"Header": "Topic/Stream",
|
||||||
"accessor": [Function],
|
"accessor": [Function],
|
||||||
"id": "topic",
|
"id": "source",
|
||||||
"show": true,
|
"show": true,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
|
@ -228,7 +199,7 @@ exports[`tasks view matches snapshot 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"Cell": [Function],
|
"Cell": [Function],
|
||||||
"Header": "Actions",
|
"Header": "Actions",
|
||||||
"accessor": "id",
|
"accessor": "supervisor_id",
|
||||||
"filterable": false,
|
"filterable": false,
|
||||||
"id": "actions",
|
"id": "actions",
|
||||||
"show": true,
|
"show": true,
|
||||||
|
@ -380,12 +351,12 @@ exports[`tasks view matches snapshot 1`] = `
|
||||||
<Blueprint3.Menu>
|
<Blueprint3.Menu>
|
||||||
<Blueprint3.MenuItem
|
<Blueprint3.MenuItem
|
||||||
disabled={false}
|
disabled={false}
|
||||||
icon="cloud-upload"
|
icon="application"
|
||||||
multiline={false}
|
multiline={false}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
popoverProps={Object {}}
|
popoverProps={Object {}}
|
||||||
shouldDismissPopover={true}
|
shouldDismissPopover={true}
|
||||||
text="Go to data loader"
|
text="View SQL query for table"
|
||||||
/>
|
/>
|
||||||
<Blueprint3.MenuItem
|
<Blueprint3.MenuItem
|
||||||
disabled={false}
|
disabled={false}
|
||||||
|
@ -414,44 +385,6 @@ exports[`tasks view matches snapshot 1`] = `
|
||||||
transitionDuration={300}
|
transitionDuration={300}
|
||||||
usePortal={true}
|
usePortal={true}
|
||||||
wrapperTagName="span"
|
wrapperTagName="span"
|
||||||
>
|
|
||||||
<Blueprint3.Button
|
|
||||||
icon="plus"
|
|
||||||
text="Submit task"
|
|
||||||
/>
|
|
||||||
</Blueprint3.Popover>
|
|
||||||
<Blueprint3.Popover
|
|
||||||
boundary="scrollParent"
|
|
||||||
captureDismiss={false}
|
|
||||||
content={
|
|
||||||
<Blueprint3.Menu>
|
|
||||||
<Blueprint3.MenuItem
|
|
||||||
disabled={false}
|
|
||||||
icon="application"
|
|
||||||
multiline={false}
|
|
||||||
onClick={[Function]}
|
|
||||||
popoverProps={Object {}}
|
|
||||||
shouldDismissPopover={true}
|
|
||||||
text="View SQL query for table"
|
|
||||||
/>
|
|
||||||
</Blueprint3.Menu>
|
|
||||||
}
|
|
||||||
defaultIsOpen={false}
|
|
||||||
disabled={false}
|
|
||||||
fill={false}
|
|
||||||
hasBackdrop={false}
|
|
||||||
hoverCloseDelay={300}
|
|
||||||
hoverOpenDelay={150}
|
|
||||||
inheritDarkTheme={true}
|
|
||||||
interactionKind="click"
|
|
||||||
minimal={false}
|
|
||||||
modifiers={Object {}}
|
|
||||||
openOnTargetFocus={true}
|
|
||||||
position="bottom-left"
|
|
||||||
targetTagName="span"
|
|
||||||
transitionDuration={300}
|
|
||||||
usePortal={true}
|
|
||||||
wrapperTagName="span"
|
|
||||||
>
|
>
|
||||||
<Blueprint3.Button
|
<Blueprint3.Button
|
||||||
icon="more"
|
icon="more"
|
||||||
|
|
|
@ -32,7 +32,7 @@ describe('tasks view', () => {
|
||||||
goToQuery={() => {}}
|
goToQuery={() => {}}
|
||||||
goToMiddleManager={() => {}}
|
goToMiddleManager={() => {}}
|
||||||
goToLoadData={() => {}}
|
goToLoadData={() => {}}
|
||||||
noSqlMode={false}
|
capabilities="full"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
expect(taskView).toMatchSnapshot();
|
expect(taskView).toMatchSnapshot();
|
||||||
|
|
|
@ -63,7 +63,9 @@ import {
|
||||||
QueryManager,
|
QueryManager,
|
||||||
} from '../../utils';
|
} from '../../utils';
|
||||||
import { BasicAction } from '../../utils/basic-action';
|
import { BasicAction } from '../../utils/basic-action';
|
||||||
|
import { Capabilities } from '../../utils/capabilities';
|
||||||
import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array';
|
import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array';
|
||||||
|
import { deepGet } from '../../utils/object-change';
|
||||||
|
|
||||||
import './tasks-view.scss';
|
import './tasks-view.scss';
|
||||||
|
|
||||||
|
@ -86,6 +88,28 @@ const taskTableColumns: string[] = [
|
||||||
ACTION_COLUMN_LABEL,
|
ACTION_COLUMN_LABEL,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
interface SupervisorQueryResultRow {
|
||||||
|
supervisor_id: string;
|
||||||
|
type: string;
|
||||||
|
source: string;
|
||||||
|
state: string;
|
||||||
|
detailed_state: string;
|
||||||
|
suspended: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TaskQueryResultRow {
|
||||||
|
task_id: string;
|
||||||
|
group_id: string;
|
||||||
|
type: string;
|
||||||
|
created_time: string;
|
||||||
|
datasource: string;
|
||||||
|
duration: number;
|
||||||
|
error_msg: string | null;
|
||||||
|
location: string | null;
|
||||||
|
status: string;
|
||||||
|
rank: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TasksViewProps {
|
export interface TasksViewProps {
|
||||||
taskId: string | undefined;
|
taskId: string | undefined;
|
||||||
datasourceId: string | undefined;
|
datasourceId: string | undefined;
|
||||||
|
@ -94,12 +118,12 @@ export interface TasksViewProps {
|
||||||
goToQuery: (initSql: string) => void;
|
goToQuery: (initSql: string) => void;
|
||||||
goToMiddleManager: (middleManager: string) => void;
|
goToMiddleManager: (middleManager: string) => void;
|
||||||
goToLoadData: (supervisorId?: string, taskId?: string) => void;
|
goToLoadData: (supervisorId?: string, taskId?: string) => void;
|
||||||
noSqlMode: boolean;
|
capabilities: Capabilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TasksViewState {
|
export interface TasksViewState {
|
||||||
supervisorsLoading: boolean;
|
supervisorsLoading: boolean;
|
||||||
supervisors: any[];
|
supervisors?: SupervisorQueryResultRow[];
|
||||||
supervisorsError?: string;
|
supervisorsError?: string;
|
||||||
|
|
||||||
resumeSupervisorId?: string;
|
resumeSupervisorId?: string;
|
||||||
|
@ -112,7 +136,7 @@ export interface TasksViewState {
|
||||||
showTerminateAllSupervisors: boolean;
|
showTerminateAllSupervisors: boolean;
|
||||||
|
|
||||||
tasksLoading: boolean;
|
tasksLoading: boolean;
|
||||||
tasks?: any[];
|
tasks?: TaskQueryResultRow[];
|
||||||
tasksError?: string;
|
tasksError?: string;
|
||||||
|
|
||||||
taskFilter: Filter[];
|
taskFilter: Filter[];
|
||||||
|
@ -135,23 +159,6 @@ export interface TasksViewState {
|
||||||
hiddenSupervisorColumns: LocalStorageBackedArray<string>;
|
hiddenSupervisorColumns: LocalStorageBackedArray<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TaskQueryResultRow {
|
|
||||||
created_time: string;
|
|
||||||
datasource: string;
|
|
||||||
duration: number;
|
|
||||||
error_msg: string | null;
|
|
||||||
location: string | null;
|
|
||||||
status: string;
|
|
||||||
task_id: string;
|
|
||||||
type: string;
|
|
||||||
rank: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SupervisorQueryResultRow {
|
|
||||||
id: string;
|
|
||||||
spec: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
function statusToColor(status: string): string {
|
function statusToColor(status: string): string {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'RUNNING':
|
case 'RUNNING':
|
||||||
|
@ -189,8 +196,8 @@ function stateToColor(status: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TasksView extends React.PureComponent<TasksViewProps, TasksViewState> {
|
export class TasksView extends React.PureComponent<TasksViewProps, TasksViewState> {
|
||||||
private supervisorQueryManager: QueryManager<null, SupervisorQueryResultRow[]>;
|
private supervisorQueryManager: QueryManager<Capabilities, SupervisorQueryResultRow[]>;
|
||||||
private taskQueryManager: QueryManager<boolean, TaskQueryResultRow[]>;
|
private taskQueryManager: QueryManager<Capabilities, TaskQueryResultRow[]>;
|
||||||
static statusRanking: Record<string, number> = {
|
static statusRanking: Record<string, number> = {
|
||||||
RUNNING: 4,
|
RUNNING: 4,
|
||||||
PENDING: 3,
|
PENDING: 3,
|
||||||
|
@ -199,6 +206,9 @@ export class TasksView extends React.PureComponent<TasksViewProps, TasksViewStat
|
||||||
FAILED: 1,
|
FAILED: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static SUPERVISOR_SQL = `SELECT "supervisor_id", "type", "source", "state", "detailed_state", "suspended"
|
||||||
|
FROM sys.supervisors`;
|
||||||
|
|
||||||
static TASK_SQL = `SELECT
|
static TASK_SQL = `SELECT
|
||||||
"task_id", "group_id", "type", "datasource", "created_time", "location", "duration", "error_msg",
|
"task_id", "group_id", "type", "datasource", "created_time", "location", "duration", "error_msg",
|
||||||
CASE WHEN "status" = 'RUNNING' THEN "runner_status" ELSE "status" END AS "status",
|
CASE WHEN "status" = 'RUNNING' THEN "runner_status" ELSE "status" END AS "status",
|
||||||
|
@ -248,9 +258,28 @@ ORDER BY "rank" DESC, "created_time" DESC`;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.supervisorQueryManager = new QueryManager({
|
this.supervisorQueryManager = new QueryManager({
|
||||||
processQuery: async () => {
|
processQuery: async capabilities => {
|
||||||
const resp = await axios.get('/druid/indexer/v1/supervisor?full');
|
if (capabilities !== 'no-sql') {
|
||||||
return resp.data;
|
return await queryDruidSql({
|
||||||
|
query: TasksView.SUPERVISOR_SQL,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const supervisors = (await axios.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: Number(deepGet(sup, 'suspended')),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onStateChange: ({ result, loading, error }) => {
|
onStateChange: ({ result, loading, error }) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -262,8 +291,8 @@ ORDER BY "rank" DESC, "created_time" DESC`;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.taskQueryManager = new QueryManager({
|
this.taskQueryManager = new QueryManager({
|
||||||
processQuery: async noSqlMode => {
|
processQuery: async capabilities => {
|
||||||
if (!noSqlMode) {
|
if (capabilities !== 'no-sql') {
|
||||||
return await queryDruidSql({
|
return await queryDruidSql({
|
||||||
query: TasksView.TASK_SQL,
|
query: TasksView.TASK_SQL,
|
||||||
});
|
});
|
||||||
|
@ -280,7 +309,7 @@ ORDER BY "rank" DESC, "created_time" DESC`;
|
||||||
return TasksView.parseTasks(resp.data);
|
return TasksView.parseTasks(resp.data);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
return ([] as TaskQueryResultRow[]).concat.apply([], result);
|
return result.flat();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onStateChange: ({ result, loading, error }) => {
|
onStateChange: ({ result, loading, error }) => {
|
||||||
|
@ -296,14 +325,15 @@ ORDER BY "rank" DESC, "created_time" DESC`;
|
||||||
static parseTasks = (data: any[]): TaskQueryResultRow[] => {
|
static parseTasks = (data: any[]): TaskQueryResultRow[] => {
|
||||||
return data.map((d: any) => {
|
return data.map((d: any) => {
|
||||||
return {
|
return {
|
||||||
|
task_id: d.id,
|
||||||
|
group_id: d.groupId,
|
||||||
|
type: d.type,
|
||||||
created_time: d.createdTime,
|
created_time: d.createdTime,
|
||||||
datasource: d.dataSource,
|
datasource: d.dataSource,
|
||||||
duration: d.duration ? d.duration : 0,
|
duration: d.duration ? d.duration : 0,
|
||||||
error_msg: d.errorMsg,
|
error_msg: d.errorMsg,
|
||||||
location: d.location.host ? `${d.location.host}:${d.location.port}` : null,
|
location: d.location.host ? `${d.location.host}:${d.location.port}` : null,
|
||||||
status: d.statusCode === 'RUNNING' ? d.runnerStatusCode : d.statusCode,
|
status: d.statusCode === 'RUNNING' ? d.runnerStatusCode : d.statusCode,
|
||||||
task_id: d.id,
|
|
||||||
type: d.typTasksView,
|
|
||||||
rank:
|
rank:
|
||||||
TasksView.statusRanking[d.statusCode === 'RUNNING' ? d.runnerStatusCode : d.statusCode],
|
TasksView.statusRanking[d.statusCode === 'RUNNING' ? d.runnerStatusCode : d.statusCode],
|
||||||
};
|
};
|
||||||
|
@ -315,10 +345,10 @@ ORDER BY "rank" DESC, "created_time" DESC`;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
const { noSqlMode } = this.props;
|
const { capabilities } = this.props;
|
||||||
|
|
||||||
this.supervisorQueryManager.runQuery(null);
|
this.supervisorQueryManager.runQuery(capabilities);
|
||||||
this.taskQueryManager.runQuery(noSqlMode);
|
this.taskQueryManager.runQuery(capabilities);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
componentWillUnmount(): void {
|
||||||
|
@ -571,62 +601,45 @@ ORDER BY "rank" DESC, "created_time" DESC`;
|
||||||
{
|
{
|
||||||
Header: 'Datasource',
|
Header: 'Datasource',
|
||||||
id: 'datasource',
|
id: 'datasource',
|
||||||
accessor: 'id',
|
accessor: 'supervisor_id',
|
||||||
width: 300,
|
width: 300,
|
||||||
show: hiddenSupervisorColumns.exists('Datasource'),
|
show: hiddenSupervisorColumns.exists('Datasource'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Type',
|
Header: 'Type',
|
||||||
id: 'type',
|
id: 'type',
|
||||||
accessor: row => {
|
accessor: row => row.type,
|
||||||
const { spec } = row;
|
|
||||||
if (!spec) return '';
|
|
||||||
const { tuningConfig } = spec;
|
|
||||||
if (!tuningConfig) return '';
|
|
||||||
return tuningConfig.type;
|
|
||||||
},
|
|
||||||
show: hiddenSupervisorColumns.exists('Type'),
|
show: hiddenSupervisorColumns.exists('Type'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Topic/Stream',
|
Header: 'Topic/Stream',
|
||||||
id: 'topic',
|
id: 'source',
|
||||||
accessor: row => {
|
accessor: row => row.source,
|
||||||
const { spec } = row;
|
|
||||||
if (!spec) return '';
|
|
||||||
const { ioConfig } = spec;
|
|
||||||
if (!ioConfig) return '';
|
|
||||||
return ioConfig.topic || ioConfig.stream || '';
|
|
||||||
},
|
|
||||||
show: hiddenSupervisorColumns.exists('Topic/Stream'),
|
show: hiddenSupervisorColumns.exists('Topic/Stream'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Status',
|
Header: 'Status',
|
||||||
id: 'status',
|
id: 'status',
|
||||||
width: 300,
|
width: 300,
|
||||||
accessor: row => {
|
accessor: row => row.detailed_state,
|
||||||
return row.detailedState;
|
Cell: row => (
|
||||||
},
|
|
||||||
Cell: row => {
|
|
||||||
const value = row.original.detailedState;
|
|
||||||
return (
|
|
||||||
<span>
|
<span>
|
||||||
<span style={{ color: stateToColor(row.original.state) }}>● </span>
|
<span style={{ color: stateToColor(row.original.state) }}>● </span>
|
||||||
{value}
|
{row.value}
|
||||||
</span>
|
</span>
|
||||||
);
|
),
|
||||||
},
|
|
||||||
show: hiddenSupervisorColumns.exists('Status'),
|
show: hiddenSupervisorColumns.exists('Status'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: ACTION_COLUMN_LABEL,
|
Header: ACTION_COLUMN_LABEL,
|
||||||
id: ACTION_COLUMN_ID,
|
id: ACTION_COLUMN_ID,
|
||||||
accessor: 'id',
|
accessor: 'supervisor_id',
|
||||||
width: ACTION_COLUMN_WIDTH,
|
width: ACTION_COLUMN_WIDTH,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
Cell: row => {
|
Cell: row => {
|
||||||
const id = row.value;
|
const id = row.value;
|
||||||
const type = row.row.type;
|
const type = row.original.type;
|
||||||
const supervisorSuspended = row.original.spec.suspended;
|
const supervisorSuspended = row.original.suspended;
|
||||||
const supervisorActions = this.getSupervisorActions(id, supervisorSuspended, type);
|
const supervisorActions = this.getSupervisorActions(id, supervisorSuspended, type);
|
||||||
return (
|
return (
|
||||||
<ActionCell
|
<ActionCell
|
||||||
|
@ -917,8 +930,22 @@ ORDER BY "rank" DESC, "created_time" DESC`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBulkSupervisorActions() {
|
renderBulkSupervisorActions() {
|
||||||
|
const { capabilities, goToQuery } = this.props;
|
||||||
|
|
||||||
const bulkSupervisorActionsMenu = (
|
const bulkSupervisorActionsMenu = (
|
||||||
<Menu>
|
<Menu>
|
||||||
|
{capabilities !== 'no-sql' && (
|
||||||
|
<MenuItem
|
||||||
|
icon={IconNames.APPLICATION}
|
||||||
|
text="View SQL query for table"
|
||||||
|
onClick={() => goToQuery(TasksView.SUPERVISOR_SQL)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<MenuItem
|
||||||
|
icon={IconNames.MANUALLY_ENTERED_DATA}
|
||||||
|
text="Submit JSON supervisor"
|
||||||
|
onClick={() => this.setState({ supervisorSpecDialogOpen: true })}
|
||||||
|
/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={IconNames.PLAY}
|
icon={IconNames.PLAY}
|
||||||
text="Resume all supervisors"
|
text="Resume all supervisors"
|
||||||
|
@ -1029,17 +1056,22 @@ ORDER BY "rank" DESC, "created_time" DESC`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBulkTasksActions() {
|
renderBulkTasksActions() {
|
||||||
const { goToQuery, noSqlMode } = this.props;
|
const { goToQuery, capabilities } = this.props;
|
||||||
|
|
||||||
const bulkTaskActionsMenu = (
|
const bulkTaskActionsMenu = (
|
||||||
<Menu>
|
<Menu>
|
||||||
{!noSqlMode && (
|
{capabilities !== 'no-sql' && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={IconNames.APPLICATION}
|
icon={IconNames.APPLICATION}
|
||||||
text="View SQL query for table"
|
text="View SQL query for table"
|
||||||
onClick={() => goToQuery(TasksView.TASK_SQL)}
|
onClick={() => goToQuery(TasksView.TASK_SQL)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<MenuItem
|
||||||
|
icon={IconNames.MANUALLY_ENTERED_DATA}
|
||||||
|
text="Submit JSON task"
|
||||||
|
onClick={() => this.setState({ taskSpecDialogOpen: true })}
|
||||||
|
/>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1053,7 +1085,6 @@ ORDER BY "rank" DESC, "created_time" DESC`;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
const { goToLoadData } = this.props;
|
|
||||||
const {
|
const {
|
||||||
groupTasksBy,
|
groupTasksBy,
|
||||||
supervisorSpecDialogOpen,
|
supervisorSpecDialogOpen,
|
||||||
|
@ -1068,36 +1099,6 @@ ORDER BY "rank" DESC, "created_time" DESC`;
|
||||||
hiddenTaskColumns,
|
hiddenTaskColumns,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const submitSupervisorMenu = (
|
|
||||||
<Menu>
|
|
||||||
<MenuItem
|
|
||||||
icon={IconNames.CLOUD_UPLOAD}
|
|
||||||
text="Go to data loader"
|
|
||||||
onClick={() => goToLoadData()}
|
|
||||||
/>
|
|
||||||
<MenuItem
|
|
||||||
icon={IconNames.MANUALLY_ENTERED_DATA}
|
|
||||||
text="Submit JSON supervisor"
|
|
||||||
onClick={() => this.setState({ supervisorSpecDialogOpen: true })}
|
|
||||||
/>
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
|
|
||||||
const submitTaskMenu = (
|
|
||||||
<Menu>
|
|
||||||
<MenuItem
|
|
||||||
icon={IconNames.CLOUD_UPLOAD}
|
|
||||||
text="Go to data loader"
|
|
||||||
onClick={() => goToLoadData()}
|
|
||||||
/>
|
|
||||||
<MenuItem
|
|
||||||
icon={IconNames.MANUALLY_ENTERED_DATA}
|
|
||||||
text="Submit JSON task"
|
|
||||||
onClick={() => this.setState({ taskSpecDialogOpen: true })}
|
|
||||||
/>
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SplitterLayout
|
<SplitterLayout
|
||||||
|
@ -1117,9 +1118,6 @@ ORDER BY "rank" DESC, "created_time" DESC`;
|
||||||
localStorageKey={LocalStorageKeys.SUPERVISORS_REFRESH_RATE}
|
localStorageKey={LocalStorageKeys.SUPERVISORS_REFRESH_RATE}
|
||||||
onRefresh={auto => this.supervisorQueryManager.rerunLastQuery(auto)}
|
onRefresh={auto => this.supervisorQueryManager.rerunLastQuery(auto)}
|
||||||
/>
|
/>
|
||||||
<Popover content={submitSupervisorMenu} position={Position.BOTTOM_LEFT}>
|
|
||||||
<Button icon={IconNames.PLUS} text="Submit supervisor" />
|
|
||||||
</Popover>
|
|
||||||
{this.renderBulkSupervisorActions()}
|
{this.renderBulkSupervisorActions()}
|
||||||
<TableColumnSelector
|
<TableColumnSelector
|
||||||
columns={supervisorTableColumns}
|
columns={supervisorTableColumns}
|
||||||
|
@ -1172,9 +1170,6 @@ ORDER BY "rank" DESC, "created_time" DESC`;
|
||||||
localStorageKey={LocalStorageKeys.TASKS_REFRESH_RATE}
|
localStorageKey={LocalStorageKeys.TASKS_REFRESH_RATE}
|
||||||
onRefresh={auto => this.taskQueryManager.rerunLastQuery(auto)}
|
onRefresh={auto => this.taskQueryManager.rerunLastQuery(auto)}
|
||||||
/>
|
/>
|
||||||
<Popover content={submitTaskMenu} position={Position.BOTTOM_LEFT}>
|
|
||||||
<Button icon={IconNames.PLUS} text="Submit task" />
|
|
||||||
</Popover>
|
|
||||||
{this.renderBulkTasksActions()}
|
{this.renderBulkTasksActions()}
|
||||||
<TableColumnSelector
|
<TableColumnSelector
|
||||||
columns={taskTableColumns}
|
columns={taskTableColumns}
|
||||||
|
|
Loading…
Reference in New Issue