diff --git a/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap b/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap index 61ae684d18a..6cb0d5421d4 100644 --- a/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap +++ b/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap @@ -10,7 +10,7 @@ exports[`header bar matches snapshot 1`] = ` - + } + content={ + + } defaultIsOpen={false} disabled={false} fill={false} @@ -85,7 +96,6 @@ exports[`header bar matches snapshot 1`] = ` wrapperTagName="span" > diff --git a/web-console/src/components/header-bar/header-bar.scss b/web-console/src/components/header-bar/header-bar.scss index b04847226a7..d21c922ca45 100644 --- a/web-console/src/components/header-bar/header-bar.scss +++ b/web-console/src/components/header-bar/header-bar.scss @@ -21,7 +21,7 @@ .header-bar { overflow: hidden; - .logo { + .druid-logo { position: relative; width: 100px; height: $header-bar-height; diff --git a/web-console/src/components/header-bar/header-bar.spec.tsx b/web-console/src/components/header-bar/header-bar.spec.tsx index 8e79ddfd1df..9f3f540eeeb 100644 --- a/web-console/src/components/header-bar/header-bar.spec.tsx +++ b/web-console/src/components/header-bar/header-bar.spec.tsx @@ -19,12 +19,14 @@ import { shallow } from 'enzyme'; import React from 'react'; +import { Capabilities } from '../../utils/capabilities'; + import { HeaderBar } from './header-bar'; describe('header bar', () => { it('matches snapshot', () => { const headerBar = shallow( - , + , ); expect(headerBar).toMatchSnapshot(); }); diff --git a/web-console/src/components/header-bar/header-bar.tsx b/web-console/src/components/header-bar/header-bar.tsx index e81ee12743a..7f5c78e03c3 100644 --- a/web-console/src/components/header-bar/header-bar.tsx +++ b/web-console/src/components/header-bar/header-bar.tsx @@ -55,12 +55,12 @@ export type HeaderActiveTab = | 'datasources' | 'segments' | 'tasks' - | 'servers' + | 'services' | 'lookups'; -function Logo() { +const DruidLogo = React.memo(function DruidLogo() { return ( -
+
); +}); + +interface LegacyMenuProps { + capabilities: Capabilities; } -function LegacyMenu() { +const LegacyMenu = React.memo(function LegacyMenu(props: LegacyMenuProps) { + const { capabilities } = props; + return ( ); -} +}); export interface HeaderBarProps { active: HeaderActiveTab; @@ -171,22 +179,26 @@ export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) { icon={IconNames.PULSE} text="Druid Doctor" onClick={() => setDoctorDialogOpen(true)} + disabled={!capabilities.hasEverything()} /> setCoordinatorDynamicConfigDialogOpen(true)} + disabled={!capabilities.hasCoordinatorAccess()} /> setOverlordDynamicConfigDialogOpen(true)} + disabled={!capabilities.hasOverlordAccess()} /> ); @@ -195,7 +207,7 @@ export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) { - + @@ -206,7 +218,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'} + disabled={!capabilities.hasEverything()} /> @@ -233,10 +245,10 @@ export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) { /> @@ -246,29 +258,20 @@ export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) { icon={IconNames.APPLICATION} text="Query" href="#query" + disabled={!capabilities.hasQuerying()} /> {!hideLegacy && ( } + content={} position={Position.BOTTOM_RIGHT} - disabled={capabilities === 'no-proxy'} > -
diff --git a/web-console/src/views/home-view/datasources-card/datasources-card.spec.tsx b/web-console/src/views/home-view/datasources-card/datasources-card.spec.tsx index da5dd08f70a..9195c62ad12 100644 --- a/web-console/src/views/home-view/datasources-card/datasources-card.spec.tsx +++ b/web-console/src/views/home-view/datasources-card/datasources-card.spec.tsx @@ -19,11 +19,13 @@ import { render } from '@testing-library/react'; import React from 'react'; +import { Capabilities } from '../../../utils/capabilities'; + import { DatasourcesCard } from './datasources-card'; describe('datasources card', () => { it('matches snapshot', () => { - const datasourcesCard = ; + const datasourcesCard = ; const { container } = render(datasourcesCard); expect(container.firstChild).toMatchSnapshot(); diff --git a/web-console/src/views/home-view/datasources-card/datasources-card.tsx b/web-console/src/views/home-view/datasources-card/datasources-card.tsx index 4b888a68173..bed26d7a76b 100644 --- a/web-console/src/views/home-view/datasources-card/datasources-card.tsx +++ b/web-console/src/views/home-view/datasources-card/datasources-card.tsx @@ -50,14 +50,17 @@ export class DatasourcesCard extends React.PureComponent< this.datasourceQueryManager = new QueryManager({ processQuery: async capabilities => { let datasources: string[]; - if (capabilities !== 'no-sql') { + if (capabilities.hasSql()) { datasources = await queryDruidSql({ query: `SELECT datasource FROM sys.segments GROUP BY 1`, }); - } else { + } else if (capabilities.hasCoordinatorAccess()) { const datasourcesResp = await axios.get('/druid/coordinator/v1/datasources'); datasources = datasourcesResp.data; + } else { + throw new Error(`must have SQL or coordinator access`); } + return datasources.length; }, onStateChange: ({ result, loading, error }) => { diff --git a/web-console/src/views/home-view/home-view.scss b/web-console/src/views/home-view/home-view.scss index 0a2d82dbe7d..e5687504741 100644 --- a/web-console/src/views/home-view/home-view.scss +++ b/web-console/src/views/home-view/home-view.scss @@ -20,7 +20,7 @@ .home-view { display: grid; - grid-gap: $standard-padding; + gap: $standard-padding; grid-template-columns: 1fr 1fr 1fr 1fr; & > a { diff --git a/web-console/src/views/home-view/home-view.spec.tsx b/web-console/src/views/home-view/home-view.spec.tsx index 5d6f1f1177b..d329dc1c6a8 100644 --- a/web-console/src/views/home-view/home-view.spec.tsx +++ b/web-console/src/views/home-view/home-view.spec.tsx @@ -19,11 +19,13 @@ import { shallow } from 'enzyme'; import React from 'react'; +import { Capabilities } from '../../utils/capabilities'; + import { HomeView } from './home-view'; describe('home view', () => { it('matches snapshot', () => { - const homeView = shallow(); + const homeView = shallow(); expect(homeView).toMatchSnapshot(); }); }); diff --git a/web-console/src/views/home-view/home-view.tsx b/web-console/src/views/home-view/home-view.tsx index afcf33d0ada..4295890aa9c 100644 --- a/web-console/src/views/home-view/home-view.tsx +++ b/web-console/src/views/home-view/home-view.tsx @@ -23,7 +23,7 @@ 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'; -import { ServersCard } from './servers-card/servers-card'; +import { ServicesCard } from './services-card/services-card'; import { StatusCard } from './status-card/status-card'; import { SupervisorsCard } from './supervisors-card/supervisors-card'; import { TasksCard } from './tasks-card/tasks-card'; @@ -44,7 +44,7 @@ export const HomeView = React.memo(function HomeView(props: HomeViewProps) { - + ); diff --git a/web-console/src/views/home-view/segments-card/segments-card.spec.tsx b/web-console/src/views/home-view/segments-card/segments-card.spec.tsx index 87685dc0095..e9988fcd4af 100644 --- a/web-console/src/views/home-view/segments-card/segments-card.spec.tsx +++ b/web-console/src/views/home-view/segments-card/segments-card.spec.tsx @@ -19,11 +19,13 @@ import { render } from '@testing-library/react'; import React from 'react'; +import { Capabilities } from '../../../utils/capabilities'; + import { SegmentsCard } from './segments-card'; describe('segments card', () => { it('matches snapshot', () => { - const segmentsCard = ; + const segmentsCard = ; const { container } = render(segmentsCard); expect(container.firstChild).toMatchSnapshot(); diff --git a/web-console/src/views/home-view/segments-card/segments-card.tsx b/web-console/src/views/home-view/segments-card/segments-card.tsx index 1f81af18b6a..94186a63f94 100644 --- a/web-console/src/views/home-view/segments-card/segments-card.tsx +++ b/web-console/src/views/home-view/segments-card/segments-card.tsx @@ -50,7 +50,15 @@ export class SegmentsCard extends React.PureComponent { - if (capabilities === 'no-sql') { + if (capabilities.hasSql()) { + const segments = await queryDruidSql({ + query: `SELECT + COUNT(*) as "count", + COUNT(*) FILTER (WHERE is_available = 0) as "unavailable" +FROM sys.segments`, + }); + return segments.length === 1 ? segments[0] : null; + } else if (capabilities.hasCoordinatorAccess()) { const loadstatusResp = await axios.get('/druid/coordinator/v1/loadstatus?simple'); const loadstatus = loadstatusResp.data; const unavailableSegmentNum = sum(Object.keys(loadstatus), key => loadstatus[key]); @@ -66,13 +74,7 @@ export class SegmentsCard extends React.PureComponent { diff --git a/web-console/src/views/home-view/servers-card/__snapshots__/servers-card.spec.tsx.snap b/web-console/src/views/home-view/services-card/__snapshots__/services-card.spec.tsx.snap similarity index 87% rename from web-console/src/views/home-view/servers-card/__snapshots__/servers-card.spec.tsx.snap rename to web-console/src/views/home-view/services-card/__snapshots__/services-card.spec.tsx.snap index 770bab484ce..1b28cdd4485 100644 --- a/web-console/src/views/home-view/servers-card/__snapshots__/servers-card.spec.tsx.snap +++ b/web-console/src/views/home-view/services-card/__snapshots__/services-card.spec.tsx.snap @@ -1,9 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`servers card matches snapshot 1`] = ` +exports[`services card matches snapshot 1`] = `
  - Servers + Services

Loading... diff --git a/web-console/src/views/home-view/servers-card/servers-card.spec.tsx b/web-console/src/views/home-view/services-card/services-card.spec.tsx similarity index 78% rename from web-console/src/views/home-view/servers-card/servers-card.spec.tsx rename to web-console/src/views/home-view/services-card/services-card.spec.tsx index 48ea6a981a6..dfec084662d 100644 --- a/web-console/src/views/home-view/servers-card/servers-card.spec.tsx +++ b/web-console/src/views/home-view/services-card/services-card.spec.tsx @@ -19,13 +19,15 @@ import { render } from '@testing-library/react'; import React from 'react'; -import { ServersCard } from './servers-card'; +import { Capabilities } from '../../../utils/capabilities'; -describe('servers card', () => { +import { ServicesCard } from './services-card'; + +describe('services card', () => { it('matches snapshot', () => { - const serversCard = ; + const servicesCard = ; - const { container } = render(serversCard); + const { container } = render(servicesCard); expect(container.firstChild).toMatchSnapshot(); }); }); diff --git a/web-console/src/views/home-view/servers-card/servers-card.tsx b/web-console/src/views/home-view/services-card/services-card.tsx similarity index 61% rename from web-console/src/views/home-view/servers-card/servers-card.tsx rename to web-console/src/views/home-view/services-card/services-card.tsx index 9c28390bd37..2d7915dde64 100644 --- a/web-console/src/views/home-view/servers-card/servers-card.tsx +++ b/web-console/src/views/home-view/services-card/services-card.tsx @@ -24,12 +24,12 @@ import { compact, lookupBy, pluralIfNeeded, queryDruidSql, QueryManager } from ' import { Capabilities } from '../../../utils/capabilities'; import { HomeViewCard } from '../home-view-card/home-view-card'; -export interface ServersCardProps { +export interface ServicesCardProps { capabilities: Capabilities; } -export interface ServersCardState { - serverCountLoading: boolean; +export interface ServicesCardState { + serviceCountLoading: boolean; coordinatorCount: number; overlordCount: number; routerCount: number; @@ -38,10 +38,10 @@ export interface ServersCardState { middleManagerCount: number; peonCount: number; indexerCount: number; - serverCountError?: string; + serviceCountError?: string; } -export class ServersCard extends React.PureComponent { +export class ServicesCard extends React.PureComponent { static renderPluralIfNeededPair( count1: number, singular1: string, @@ -56,12 +56,12 @@ export class ServersCard extends React.PureComponent{text}

; } - private serverQueryManager: QueryManager; + private serviceQueryManager: QueryManager; - constructor(props: ServersCardProps, context: any) { + constructor(props: ServicesCardProps, context: any) { super(props, context); this.state = { - serverCountLoading: false, + serviceCountLoading: false, coordinatorCount: 0, overlordCount: 0, routerCount: 0, @@ -72,29 +72,37 @@ export class ServersCard extends React.PureComponent { - if (capabilities === 'no-sql') { - const serversResp = await axios.get('/druid/coordinator/v1/servers?simple'); - const middleManagerResp = await axios.get('/druid/indexer/v1/workers'); - return { - historical: serversResp.data.filter((s: any) => s.type === 'historical').length, - middle_manager: middleManagerResp.data.length, - peon: serversResp.data.filter((s: any) => s.type === 'indexer-executor').length, - }; - } else { - const serverCountsFromQuery: { - server_type: string; + if (capabilities.hasSql()) { + const serviceCountsFromQuery: { + service_type: string; count: number; }[] = await queryDruidSql({ - query: `SELECT server_type, COUNT(*) as "count" FROM sys.servers GROUP BY 1`, + query: `SELECT server_type AS "service_type", COUNT(*) as "count" FROM sys.servers GROUP BY 1`, }); - return lookupBy(serverCountsFromQuery, x => x.server_type, x => x.count); + return lookupBy(serviceCountsFromQuery, x => x.service_type, x => x.count); + } else if (capabilities.hasCoordinatorAccess() || capabilities.hasOverlordAccess()) { + const services = capabilities.hasCoordinatorAccess() + ? (await axios.get('/druid/coordinator/v1/servers?simple')).data + : []; + + const middleManager = capabilities.hasOverlordAccess() + ? (await axios.get('/druid/indexer/v1/workers')).data + : []; + + return { + historical: services.filter((s: any) => s.type === 'historical').length, + middle_manager: middleManager.length, + peon: services.filter((s: any) => s.type === 'indexer-executor').length, + }; + } else { + throw new Error(`must have SQL or coordinator/overlord access`); } }, onStateChange: ({ result, loading, error }) => { this.setState({ - serverCountLoading: loading, + serviceCountLoading: loading, coordinatorCount: result ? result.coordinator : 0, overlordCount: result ? result.overlord : 0, routerCount: result ? result.router : 0, @@ -103,7 +111,7 @@ export class ServersCard extends React.PureComponent - {ServersCard.renderPluralIfNeededPair( + {ServicesCard.renderPluralIfNeededPair( overlordCount, 'overlord', coordinatorCount, 'coordinator', )} - {ServersCard.renderPluralIfNeededPair(routerCount, 'router', brokerCount, 'broker')} - {ServersCard.renderPluralIfNeededPair( + {ServicesCard.renderPluralIfNeededPair(routerCount, 'router', brokerCount, 'broker')} + {ServicesCard.renderPluralIfNeededPair( historicalCount, 'historical', middleManagerCount, 'middle manager', )} - {ServersCard.renderPluralIfNeededPair(peonCount, 'peon', indexerCount, 'indexer')} + {ServicesCard.renderPluralIfNeededPair(peonCount, 'peon', indexerCount, 'indexer')} ); } diff --git a/web-console/src/views/home-view/supervisors-card/supervisors-card.spec.tsx b/web-console/src/views/home-view/supervisors-card/supervisors-card.spec.tsx index ac9248da3f9..ec19bef5d2c 100644 --- a/web-console/src/views/home-view/supervisors-card/supervisors-card.spec.tsx +++ b/web-console/src/views/home-view/supervisors-card/supervisors-card.spec.tsx @@ -19,11 +19,13 @@ import { render } from '@testing-library/react'; import React from 'react'; +import { Capabilities } from '../../../utils/capabilities'; + import { SupervisorsCard } from './supervisors-card'; describe('supervisors card', () => { it('matches snapshot', () => { - const supervisorsCard = ; + const supervisorsCard = ; const { container } = render(supervisorsCard); expect(container.firstChild).toMatchSnapshot(); diff --git a/web-console/src/views/home-view/supervisors-card/supervisors-card.tsx b/web-console/src/views/home-view/supervisors-card/supervisors-card.tsx index 1b4358aa72c..5b3633bfc11 100644 --- a/web-console/src/views/home-view/supervisors-card/supervisors-card.tsx +++ b/web-console/src/views/home-view/supervisors-card/supervisors-card.tsx @@ -51,14 +51,14 @@ export class SupervisorsCard extends React.PureComponent< this.supervisorQueryManager = new QueryManager({ processQuery: async capabilities => { - if (capabilities !== 'no-sql') { + if (capabilities.hasSql()) { return (await queryDruidSql({ query: `SELECT COUNT(*) FILTER (WHERE "suspended" = 0) AS "runningSupervisorCount", COUNT(*) FILTER (WHERE "suspended" = 1) AS "suspendedSupervisorCount" FROM sys.supervisors`, }))[0]; - } else { + } else if (capabilities.hasOverlordAccess()) { 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; @@ -68,6 +68,8 @@ FROM sys.supervisors`, runningSupervisorCount, suspendedSupervisorCount, }; + } else { + throw new Error(`must have SQL or overlord access`); } }, onStateChange: ({ result, loading, error }) => { diff --git a/web-console/src/views/home-view/tasks-card/tasks-card.spec.tsx b/web-console/src/views/home-view/tasks-card/tasks-card.spec.tsx index df830266c67..3398b6a7c23 100644 --- a/web-console/src/views/home-view/tasks-card/tasks-card.spec.tsx +++ b/web-console/src/views/home-view/tasks-card/tasks-card.spec.tsx @@ -19,11 +19,13 @@ import { render } from '@testing-library/react'; import React from 'react'; +import { Capabilities } from '../../../utils/capabilities'; + import { TasksCard } from './tasks-card'; describe('tasks card', () => { it('matches snapshot', () => { - const tasksCard = ; + const tasksCard = ; const { container } = render(tasksCard); expect(container.firstChild).toMatchSnapshot(); diff --git a/web-console/src/views/home-view/tasks-card/tasks-card.tsx b/web-console/src/views/home-view/tasks-card/tasks-card.tsx index 044e79e2c6e..47cbe9e98a7 100644 --- a/web-console/src/views/home-view/tasks-card/tasks-card.tsx +++ b/web-console/src/views/home-view/tasks-card/tasks-card.tsx @@ -54,7 +54,16 @@ export class TasksCard extends React.PureComponent { - if (capabilities === 'no-sql') { + if (capabilities.hasSql()) { + const taskCountsFromQuery: { status: string; count: number }[] = await queryDruidSql({ + query: `SELECT + CASE WHEN "status" = 'RUNNING' THEN "runner_status" ELSE "status" END AS "status", + COUNT (*) AS "count" +FROM sys.tasks +GROUP BY 1`, + }); + return lookupBy(taskCountsFromQuery, x => x.status, x => x.count); + } else if (capabilities.hasOverlordAccess()) { 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'); @@ -67,14 +76,7 @@ export class TasksCard extends React.PureComponent x.status, x => x.count); + throw new Error(`must have SQL or overlord access`); } }, onStateChange: ({ result, loading, error }) => { diff --git a/web-console/src/views/index.ts b/web-console/src/views/index.ts index bea772fc2cb..1dd24f0f804 100644 --- a/web-console/src/views/index.ts +++ b/web-console/src/views/index.ts @@ -21,6 +21,6 @@ export * from './home-view/home-view'; export * from './load-data-view/load-data-view'; export * from './lookups-view/lookups-view'; export * from './segments-view/segments-view'; -export * from './servers-view/servers-view'; +export * from './services-view/services-view'; export * from './query-view/query-view'; export * from './task-view/tasks-view'; diff --git a/web-console/src/views/load-data-view/load-data-view.tsx b/web-console/src/views/load-data-view/load-data-view.tsx index e471c048e5d..094c488a81e 100644 --- a/web-console/src/views/load-data-view/load-data-view.tsx +++ b/web-console/src/views/load-data-view/load-data-view.tsx @@ -326,7 +326,7 @@ export class LoadDataView extends React.PureComponent {this.renderStepNav()} + {step === 'loading' && } + {step === 'welcome' && this.renderWelcomeStep()} {step === 'connect' && this.renderConnectStep()} {step === 'parser' && this.renderParserStep()} @@ -548,7 +550,6 @@ export class LoadDataView extends React.PureComponent @@ -1071,7 +1072,7 @@ export class LoadDataView extends React.PureComponent - This path must be available on the local filesystem of all Druid servers. + This path must be available on the local filesystem of all Druid services. )} @@ -3000,10 +3001,6 @@ export class LoadDataView extends React.PureComponent; - } - renderSpecStep() { const { spec, submitting } = this.state; diff --git a/web-console/src/views/segments-view/segments-view.spec.tsx b/web-console/src/views/segments-view/segments-view.spec.tsx index 8dced7101a3..5dccedf388a 100644 --- a/web-console/src/views/segments-view/segments-view.spec.tsx +++ b/web-console/src/views/segments-view/segments-view.spec.tsx @@ -19,6 +19,7 @@ import { shallow } from 'enzyme'; import React from 'react'; +import { Capabilities } from '../../utils/capabilities'; import { SegmentsView } from '../segments-view/segments-view'; describe('segments-view', () => { @@ -28,7 +29,7 @@ describe('segments-view', () => { datasource={'test'} onlyUnavailable={false} goToQuery={() => {}} - capabilities="full" + capabilities={Capabilities.FULL} />, ); expect(segmentsView).toMatchSnapshot(); diff --git a/web-console/src/views/segments-view/segments-view.tsx b/web-console/src/views/segments-view/segments-view.tsx index ea39d77a79e..1acf8a88f39 100644 --- a/web-console/src/views/segments-view/segments-view.tsx +++ b/web-console/src/views/segments-view/segments-view.tsx @@ -56,12 +56,12 @@ import { sqlQueryCustomTableFilter, } from '../../utils'; import { BasicAction } from '../../utils/basic-action'; -import { Capabilities } from '../../utils/capabilities'; +import { Capabilities, CapabilitiesMode } from '../../utils/capabilities'; import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array'; import './segments-view.scss'; -const tableColumns: Record = { +const tableColumns: Record = { full: [ 'Segment ID', 'Datasource', @@ -103,7 +103,6 @@ const tableColumns: Record = { 'Is available', 'Is overshadowed', ], - broken: ['Segment ID'], }; export interface SegmentsViewProps { @@ -339,7 +338,7 @@ export class SegmentsView extends React.PureComponent { this.setState({ segmentFilter: filtered }); }} - onFetchData={ - capabilities === 'no-sql' - ? this.fetchClientSideData - : state => { - this.setState({ - page: state.page, - pageSize: state.pageSize, - filtered: state.filtered, - sorted: state.sorted, - }); - if (this.segmentsSqlQueryManager.getLastQuery) { - this.fetchData(groupByInterval, state); - } - } - } + onFetchData={state => { + if (capabilities.hasSql()) { + this.setState({ + page: state.page, + pageSize: state.pageSize, + filtered: state.filtered, + sorted: state.sorted, + }); + if (this.segmentsSqlQueryManager.getLastQuery) { + this.fetchData(groupByInterval, state); + } + } else if (capabilities.hasCoordinatorAccess()) { + this.fetchClientSideData(state); + } + }} showPageJump={false} ofText="" pivotBy={groupByInterval ? ['interval'] : []} @@ -556,7 +555,7 @@ export class SegmentsView extends React.PureComponent (row.original.is_available ? formatNumber(row.value) : (unknown)), - show: capabilities !== 'no-sql' && hiddenColumns.exists('Num rows'), + show: capabilities.hasSql() && hiddenColumns.exists('Num rows'), }, { Header: 'Replicas', @@ -564,35 +563,35 @@ export class SegmentsView extends React.PureComponent String(Boolean(row.is_published)), Filter: makeBooleanFilter(), - show: capabilities !== 'no-sql' && hiddenColumns.exists('Is published'), + show: capabilities.hasSql() && hiddenColumns.exists('Is published'), }, { Header: 'Is realtime', id: 'is_realtime', accessor: row => String(Boolean(row.is_realtime)), Filter: makeBooleanFilter(), - show: capabilities !== 'no-sql' && hiddenColumns.exists('Is realtime'), + show: capabilities.hasSql() && hiddenColumns.exists('Is realtime'), }, { Header: 'Is available', id: 'is_available', accessor: row => String(Boolean(row.is_available)), Filter: makeBooleanFilter(), - show: capabilities !== 'no-sql' && hiddenColumns.exists('Is available'), + show: capabilities.hasSql() && hiddenColumns.exists('Is available'), }, { Header: 'Is overshadowed', id: 'is_overshadowed', accessor: row => String(Boolean(row.is_overshadowed)), Filter: makeBooleanFilter(), - show: capabilities !== 'no-sql' && hiddenColumns.exists('Is overshadowed'), + show: capabilities.hasSql() && hiddenColumns.exists('Is overshadowed'), }, { Header: ACTION_COLUMN_LABEL, @@ -618,7 +617,7 @@ export class SegmentsView extends React.PureComponent '', - show: capabilities !== 'no-proxy' && hiddenColumns.exists(ACTION_COLUMN_LABEL), + show: capabilities.hasCoordinatorAccess() && hiddenColumns.exists(ACTION_COLUMN_LABEL), }, ]} defaultPageSize={SegmentsView.PAGE_SIZE} @@ -663,7 +662,7 @@ export class SegmentsView extends React.PureComponent - {capabilities !== 'no-sql' && ( + {capabilities.hasSql() && ( { this.setState({ groupByInterval: false }); - capabilities === 'no-sql' ? this.fetchClientSideData() : this.fetchData(false); + if (capabilities.hasSql()) { + this.fetchData(false); + } else { + this.fetchClientSideData(); + } }} > None @@ -731,7 +734,7 @@ export class SegmentsView extends React.PureComponent {this.renderBulkSegmentsActions()} this.setState(prevState => ({ hiddenColumns: prevState.hiddenColumns.toggle(column), diff --git a/web-console/src/views/servers-view/__snapshots__/servers-view.spec.tsx.snap b/web-console/src/views/services-view/__snapshots__/services-view.spec.tsx.snap similarity index 96% rename from web-console/src/views/servers-view/__snapshots__/servers-view.spec.tsx.snap rename to web-console/src/views/services-view/__snapshots__/services-view.spec.tsx.snap index 266019cf524..52c25b27bc0 100755 --- a/web-console/src/views/servers-view/__snapshots__/servers-view.spec.tsx.snap +++ b/web-console/src/views/services-view/__snapshots__/services-view.spec.tsx.snap @@ -1,11 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`servers view action servers view 1`] = ` +exports[`services view action services view 1`] = `
Group by @@ -31,7 +31,7 @@ exports[`servers view action servers view 1`] = ` { - it('action servers view', () => { - const serversView = shallow( - { + it('action services view', () => { + const servicesView = shallow( + {}} goToTask={() => {}} - capabilities="full" + capabilities={Capabilities.FULL} />, ); - expect(serversView).toMatchSnapshot(); + expect(servicesView).toMatchSnapshot(); }); }); diff --git a/web-console/src/views/servers-view/servers-view.tsx b/web-console/src/views/services-view/services-view.tsx similarity index 72% rename from web-console/src/views/servers-view/servers-view.tsx rename to web-console/src/views/services-view/services-view.tsx index 7201e344efa..3ecbb036527 100644 --- a/web-console/src/views/servers-view/servers-view.tsx +++ b/web-console/src/views/services-view/services-view.tsx @@ -53,14 +53,14 @@ import { QueryManager, } from '../../utils'; import { BasicAction } from '../../utils/basic-action'; -import { Capabilities } from '../../utils/capabilities'; +import { Capabilities, CapabilitiesMode } from '../../utils/capabilities'; import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array'; import { deepGet } from '../../utils/object-change'; -import './servers-view.scss'; +import './services-view.scss'; const allColumns: string[] = [ - 'Server', + 'Service', 'Type', 'Tier', 'Host', @@ -72,11 +72,10 @@ const allColumns: string[] = [ ACTION_COLUMN_LABEL, ]; -const tableColumns: Record = { +const tableColumns: Record = { full: allColumns, 'no-sql': allColumns, - 'no-proxy': ['Server', 'Type', 'Tier', 'Host', 'Port', 'Curr size', 'Max size', 'Usage'], - broken: ['Server'], + 'no-proxy': ['Service', 'Type', 'Tier', 'Host', 'Port', 'Curr size', 'Max size', 'Usage'], }; function formatQueues( @@ -99,19 +98,19 @@ function formatQueues( return queueParts.join(', ') || 'Empty load/drop queues'; } -export interface ServersViewProps { +export interface ServicesViewProps { middleManager: string | undefined; goToQuery: (initSql: string) => void; goToTask: (taskId: string) => void; capabilities: Capabilities; } -export interface ServersViewState { - serversLoading: boolean; - servers?: any[]; - serversError?: string; - serverFilter: Filter[]; - groupServersBy?: 'server_type' | 'tier'; +export interface ServicesViewState { + servicesLoading: boolean; + services?: any[]; + servicesError?: string; + serviceFilter: Filter[]; + groupServicesBy?: 'service_type' | 'tier'; middleManagerDisableWorkerHost?: string; middleManagerEnableWorkerHost?: string; @@ -119,9 +118,9 @@ export interface ServersViewState { hiddenColumns: LocalStorageBackedArray; } -interface ServerQueryResultRow { - server: string; - server_type: string; +interface ServiceQueryResultRow { + service: string; + service_type: string; tier: string; curr_size: number; host: string; @@ -152,13 +151,13 @@ interface MiddleManagerQueryResultRow { }; } -interface ServerResultRow - extends ServerQueryResultRow, +interface ServiceResultRow + extends ServiceQueryResultRow, Partial, Partial {} -export class ServersView extends React.PureComponent { - private serverQueryManager: QueryManager; +export class ServicesView extends React.PureComponent { + private serviceQueryManager: QueryManager; // Ranking // coordinator => 7 @@ -169,8 +168,8 @@ export class ServersView extends React.PureComponent 2 // peon => 1 - static SERVER_SQL = `SELECT - "server", "server_type", "tier", "host", "plaintext_port", "tls_port", "curr_size", "max_size", + static SERVICE_SQL = `SELECT + "server" AS "service", "server_type" AS "service_type", "tier", "host", "plaintext_port", "tls_port", "curr_size", "max_size", ( CASE "server_type" WHEN 'coordinator' THEN 7 @@ -184,15 +183,15 @@ export class ServersView extends React.PureComponent { - const allServerResp = await axios.get('/druid/coordinator/v1/servers?simple'); - const allServers = allServerResp.data; - return allServers.map((s: any) => { + static async getServices(): Promise { + const allServiceResp = await axios.get('/druid/coordinator/v1/servers?simple'); + const allServices = allServiceResp.data; + return allServices.map((s: any) => { return { - server: s.host, - server_type: s.type === 'indexer-executor' ? 'peon' : s.type, + service: s.host, + service_type: s.type === 'indexer-executor' ? 'peon' : s.type, tier: s.tier, host: s.host.split(':')[0], plaintext_port: parseInt(s.host.split(':')[1], 10), @@ -203,73 +202,77 @@ ORDER BY "rank" DESC, "server" DESC`; }); } - constructor(props: ServersViewProps, context: any) { + constructor(props: ServicesViewProps, context: any) { super(props, context); this.state = { - serversLoading: true, - serverFilter: [], + servicesLoading: true, + serviceFilter: [], hiddenColumns: new LocalStorageBackedArray( - LocalStorageKeys.SERVER_TABLE_COLUMN_SELECTION, + LocalStorageKeys.SERVICE_TABLE_COLUMN_SELECTION, ), }; - this.serverQueryManager = new QueryManager({ + this.serviceQueryManager = new QueryManager({ processQuery: async capabilities => { - let servers: ServerQueryResultRow[]; - if (capabilities !== 'no-sql') { - servers = await queryDruidSql({ query: ServersView.SERVER_SQL }); + let services: ServiceQueryResultRow[]; + if (capabilities.hasSql()) { + services = await queryDruidSql({ query: ServicesView.SERVICE_SQL }); + } else if (capabilities.hasCoordinatorAccess()) { + services = await ServicesView.getServices(); } else { - servers = await ServersView.getServers(); + throw new Error(`must have SQL or coordinator access`); } - if (capabilities === 'no-proxy') { - return servers; + if (capabilities.hasCoordinatorAccess()) { + const loadQueueResponse = await axios.get('/druid/coordinator/v1/loadqueue?simple'); + const loadQueues: Record = loadQueueResponse.data; + services = services.map(s => { + const loadQueueInfo = loadQueues[s.service]; + if (loadQueueInfo) { + s = Object.assign(s, loadQueueInfo); + } + return s; + }); } - const loadQueueResponse = await axios.get('/druid/coordinator/v1/loadqueue?simple'); - const loadQueues: Record = loadQueueResponse.data; - servers = servers.map((s: any) => { - const loadQueueInfo = loadQueues[s.server]; - if (loadQueueInfo) { - s = Object.assign(s, loadQueueInfo); + if (capabilities.hasOverlordAccess()) { + let middleManagers: MiddleManagerQueryResultRow[]; + try { + const middleManagerResponse = await axios.get('/druid/indexer/v1/workers'); + middleManagers = middleManagerResponse.data; + } catch (e) { + if ( + e.response && + typeof e.response.data === 'object' && + e.response.data.error === 'Task Runner does not support worker listing' + ) { + // Swallow this error because it simply a reflection of a local task runner. + middleManagers = []; + } else { + // Otherwise re-throw. + throw e; + } } - return s; - }); - let middleManagers: MiddleManagerQueryResultRow[]; - try { - const middleManagerResponse = await axios.get('/druid/indexer/v1/workers'); - middleManagers = middleManagerResponse.data; - } catch (e) { - if ( - e.response && - typeof e.response.data === 'object' && - e.response.data.error === 'Task Runner does not support worker listing' - ) { - // Swallow this error because it simply a reflection of a local task runner. - middleManagers = []; - } else { - // Otherwise re-throw. - throw e; - } + const middleManagersLookup = lookupBy(middleManagers, m => m.worker.host); + + services = services.map(s => { + const middleManagerInfo = middleManagersLookup[s.service]; + if (middleManagerInfo) { + s = Object.assign(s, middleManagerInfo); + } + return s; + }); } - const middleManagersLookup = lookupBy(middleManagers, m => m.worker.host); - - return servers.map((s: any) => { - const middleManagerInfo = middleManagersLookup[s.server]; - if (middleManagerInfo) { - s = Object.assign(s, middleManagerInfo); - } - return s; - }); + return services; }, onStateChange: ({ result, loading, error }) => { this.setState({ - servers: result, - serversLoading: loading, - serversError: error, + services: result, + servicesLoading: loading, + servicesError: error, }); }, }); @@ -277,21 +280,21 @@ ORDER BY "rank" DESC, "server" DESC`; componentDidMount(): void { const { capabilities } = this.props; - this.serverQueryManager.runQuery(capabilities); + this.serviceQueryManager.runQuery(capabilities); } componentWillUnmount(): void { - this.serverQueryManager.terminate(); + this.serviceQueryManager.terminate(); } - renderServersTable() { + renderServicesTable() { const { capabilities } = this.props; const { - servers, - serversLoading, - serversError, - serverFilter, - groupServersBy, + services, + servicesLoading, + servicesError, + serviceFilter, + groupServicesBy, hiddenColumns, } = this.state; @@ -308,36 +311,38 @@ ORDER BY "rank" DESC, "server" DESC`; return ( { - this.setState({ serverFilter: filtered }); + this.setState({ serviceFilter: filtered }); }} - pivotBy={groupServersBy ? [groupServersBy] : []} + pivotBy={groupServicesBy ? [groupServicesBy] : []} defaultPageSize={50} columns={[ { - Header: 'Server', - accessor: 'server', + Header: 'Service', + accessor: 'service', width: 300, Aggregated: () => '', - show: hiddenColumns.exists('Server'), + show: hiddenColumns.exists('Service'), }, { Header: 'Type', - accessor: 'server_type', + accessor: 'service_type', width: 150, Cell: row => { const value = row.value; return ( { - this.setState({ serverFilter: addFilter(serverFilter, 'server_type', value) }); + this.setState({ + serviceFilter: addFilter(serviceFilter, 'service_type', value), + }); }} > {value} @@ -354,7 +359,7 @@ ORDER BY "rank" DESC, "server" DESC`; return ( { - this.setState({ serverFilter: addFilter(serverFilter, 'tier', value) }); + this.setState({ serviceFilter: addFilter(serviceFilter, 'tier', value) }); }} > {value} @@ -398,7 +403,7 @@ ORDER BY "rank" DESC, "server" DESC`; return formatBytes(totalCurr); }, Cell: row => { - if (row.aggregated || row.original.server_type !== 'historical') return ''; + if (row.aggregated || row.original.service_type !== 'historical') return ''; if (row.value === null) return ''; return formatBytes(row.value); }, @@ -417,7 +422,7 @@ ORDER BY "rank" DESC, "server" DESC`; return formatBytes(totalMax); }, Cell: row => { - if (row.aggregated || row.original.server_type !== 'historical') return ''; + if (row.aggregated || row.original.service_type !== 'historical') return ''; if (row.value === null) return ''; return formatBytes(row.value); }, @@ -429,7 +434,7 @@ ORDER BY "rank" DESC, "server" DESC`; width: 100, filterable: false, accessor: row => { - if (row.server_type === 'middle_manager') { + if (row.service_type === 'middle_manager') { return row.worker ? row.currCapacityUsed / row.worker.capacity : null; } else { return row.max_size ? row.curr_size / row.max_size : null; @@ -461,8 +466,8 @@ ORDER BY "rank" DESC, "server" DESC`; }, Cell: row => { if (row.aggregated) return ''; - const { server_type } = row.original; - switch (server_type) { + const { service_type } = row.original; + switch (service_type) { case 'historical': return fillIndicator(row.value); @@ -487,7 +492,7 @@ ORDER BY "rank" DESC, "server" DESC`; width: 400, filterable: false, accessor: row => { - if (row.server_type === 'middle_manager') { + if (row.service_type === 'middle_manager') { if (deepGet(row, 'worker.version') === '') return 'Disabled'; const details: string[] = []; @@ -504,8 +509,8 @@ ORDER BY "rank" DESC, "server" DESC`; }, Cell: row => { if (row.aggregated) return ''; - const { server_type } = row.original; - switch (server_type) { + const { service_type } = row.original; + switch (service_type) { case 'historical': const { segmentsToLoad, @@ -541,7 +546,7 @@ ORDER BY "rank" DESC, "server" DESC`; segmentsToDropSize, ); }, - show: capabilities !== 'no-proxy' && hiddenColumns.exists('Detail'), + show: capabilities.hasCoordinatorAccess() && hiddenColumns.exists('Detail'), }, { Header: ACTION_COLUMN_LABEL, @@ -555,7 +560,7 @@ ORDER BY "rank" DESC, "server" DESC`; const workerActions = this.getWorkerActions(row.value.host, disabled); return ; }, - show: capabilities !== 'no-proxy' && hiddenColumns.exists(ACTION_COLUMN_LABEL), + show: capabilities.hasOverlordAccess() && hiddenColumns.exists(ACTION_COLUMN_LABEL), }, ]} /> @@ -603,7 +608,7 @@ ORDER BY "rank" DESC, "server" DESC`; this.setState({ middleManagerDisableWorkerHost: undefined }); }} onSuccess={() => { - this.serverQueryManager.rerunLastQuery(); + this.serviceQueryManager.rerunLastQuery(); }} >

{`Are you sure you want to disable worker '${middleManagerDisableWorkerHost}'?`}

@@ -632,7 +637,7 @@ ORDER BY "rank" DESC, "server" DESC`; this.setState({ middleManagerEnableWorkerHost: undefined }); }} onSuccess={() => { - this.serverQueryManager.rerunLastQuery(); + this.serviceQueryManager.rerunLastQuery(); }} >

{`Are you sure you want to enable worker '${middleManagerEnableWorkerHost}'?`}

@@ -640,16 +645,16 @@ ORDER BY "rank" DESC, "server" DESC`; ); } - renderBulkServersActions() { + renderBulkServicesActions() { const { goToQuery, capabilities } = this.props; - const bulkserversActionsMenu = ( + const bulkservicesActionsMenu = ( - {capabilities !== 'no-sql' && ( + {capabilities.hasSql() && ( goToQuery(ServersView.SERVER_SQL)} + onClick={() => goToQuery(ServicesView.SERVICE_SQL)} /> )} @@ -657,7 +662,7 @@ ORDER BY "rank" DESC, "server" DESC`; return ( <> - + this.serverQueryManager.rerunLastQuery(auto)} - localStorageKey={LocalStorageKeys.SERVERS_REFRESH_RATE} + onRefresh={auto => this.serviceQueryManager.rerunLastQuery(auto)} + localStorageKey={LocalStorageKeys.SERVICES_REFRESH_RATE} /> - {this.renderBulkServersActions()} + {this.renderBulkServicesActions()} this.setState(prevState => ({ hiddenColumns: prevState.hiddenColumns.toggle(column), @@ -707,7 +712,7 @@ ORDER BY "rank" DESC, "server" DESC`; tableColumnsHidden={hiddenColumns.storedArray} /> - {this.renderServersTable()} + {this.renderServicesTable()} {this.renderDisableWorkerAction()} {this.renderEnableWorkerAction()}
diff --git a/web-console/src/views/task-view/tasks-view.spec.tsx b/web-console/src/views/task-view/tasks-view.spec.tsx index 4f77159eed4..fc3f29693fe 100644 --- a/web-console/src/views/task-view/tasks-view.spec.tsx +++ b/web-console/src/views/task-view/tasks-view.spec.tsx @@ -19,6 +19,8 @@ import { shallow } from 'enzyme'; import React from 'react'; +import { Capabilities } from '../../utils/capabilities'; + import { TasksView } from './tasks-view'; describe('tasks view', () => { @@ -32,7 +34,7 @@ describe('tasks view', () => { goToQuery={() => {}} goToMiddleManager={() => {}} goToLoadData={() => {}} - capabilities="full" + capabilities={Capabilities.FULL} />, ); expect(taskView).toMatchSnapshot(); diff --git a/web-console/src/views/task-view/tasks-view.tsx b/web-console/src/views/task-view/tasks-view.tsx index 0c5e7589419..e5affae394a 100644 --- a/web-console/src/views/task-view/tasks-view.tsx +++ b/web-console/src/views/task-view/tasks-view.tsx @@ -259,11 +259,11 @@ ORDER BY "rank" DESC, "created_time" DESC`; this.supervisorQueryManager = new QueryManager({ processQuery: async capabilities => { - if (capabilities !== 'no-sql') { + if (capabilities.hasSql()) { return await queryDruidSql({ query: TasksView.SUPERVISOR_SQL, }); - } else { + } else if (capabilities.hasOverlordAccess()) { 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) => { @@ -279,6 +279,8 @@ ORDER BY "rank" DESC, "created_time" DESC`; suspended: Number(deepGet(sup, 'suspended')), }; }); + } else { + throw new Error(`must have SQL or overlord access`); } }, onStateChange: ({ result, loading, error }) => { @@ -292,11 +294,11 @@ ORDER BY "rank" DESC, "created_time" DESC`; this.taskQueryManager = new QueryManager({ processQuery: async capabilities => { - if (capabilities !== 'no-sql') { + if (capabilities.hasSql()) { return await queryDruidSql({ query: TasksView.TASK_SQL, }); - } else { + } else if (capabilities.hasOverlordAccess()) { const taskEndpoints: string[] = [ 'completeTasks', 'runningTasks', @@ -310,6 +312,8 @@ ORDER BY "rank" DESC, "created_time" DESC`; }), ); return result.flat(); + } else { + throw new Error(`must have SQL or overlord access`); } }, onStateChange: ({ result, loading, error }) => { @@ -934,7 +938,7 @@ ORDER BY "rank" DESC, "created_time" DESC`; const bulkSupervisorActionsMenu = ( - {capabilities !== 'no-sql' && ( + {capabilities.hasSql() && ( - {capabilities !== 'no-sql' && ( + {capabilities.hasSql() && (