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