diff --git a/licenses.yaml b/licenses.yaml index e684073d81d..465605fa4e3 100644 --- a/licenses.yaml +++ b/licenses.yaml @@ -4751,7 +4751,7 @@ license_category: binary module: web-console license_name: Apache License version 2.0 copyright: Imply Data -version: 0.9.15 +version: 0.10.4 --- diff --git a/web-console/package-lock.json b/web-console/package-lock.json index cd7673d0a2b..fccd993c3f0 100644 --- a/web-console/package-lock.json +++ b/web-console/package-lock.json @@ -4265,11 +4265,18 @@ } }, "druid-query-toolkit": { - "version": "0.9.15", - "resolved": "https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.9.15.tgz", - "integrity": "sha512-HiJVqa6X6z/LeEU3GVxbkaP/X8JCn/9n8txoG1tSgk6edc1BZXj7F7hZpaNlAThVcZcFOYeE1gFOHzMMjK4U0A==", + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.10.4.tgz", + "integrity": "sha512-feIRTC2paOkGpWvymseMs/wn+8XfbLjlcBsXJXKxgsJtqMKBYy3f8YiN3SV/xv6CQP9Vv4nBMEoa5q8OM5KHsg==", "requires": { - "tslib": "^1.10.0" + "tslib": "^2.0.2" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } } }, "duplexer": { diff --git a/web-console/package.json b/web-console/package.json index f7cc8368063..c5c29f80082 100644 --- a/web-console/package.json +++ b/web-console/package.json @@ -68,7 +68,7 @@ "d3-axis": "^1.0.12", "d3-scale": "^3.2.0", "d3-selection": "^1.4.0", - "druid-query-toolkit": "^0.9.15", + "druid-query-toolkit": "^0.10.4", "file-saver": "^2.0.2", "fontsource-open-sans": "^3.0.9", "has-own-prop": "^2.0.0", diff --git a/web-console/script/create-sql-docs.js b/web-console/script/create-sql-docs.js index ce09be33c81..86ec0d70d75 100755 --- a/web-console/script/create-sql-docs.js +++ b/web-console/script/create-sql-docs.js @@ -61,7 +61,7 @@ const readDoc = async () => { ); } - // Make sure there are at least 5 data types for sanity + // Make sure there are at least 10 data types for sanity if (dataTypeDocs.length < 10) { throw new Error( `Did not find enough data type entries did the structure of '${readfile}' change? (found ${dataTypeDocs.length})`, diff --git a/web-console/src/bootstrap/ace.ts b/web-console/src/bootstrap/ace.ts new file mode 100644 index 00000000000..f3689a14516 --- /dev/null +++ b/web-console/src/bootstrap/ace.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +import 'brace'; // Import Ace editor and all the sub components used in the app +import 'brace/ext/language_tools'; +import 'brace/theme/solarized_dark'; + +import '../ace-modes/dsql'; +import '../ace-modes/hjson'; diff --git a/web-console/src/bootstrap/react-table-defaults.tsx b/web-console/src/bootstrap/react-table-defaults.tsx index e397d3b9ea1..b85a0666063 100644 --- a/web-console/src/bootstrap/react-table-defaults.tsx +++ b/web-console/src/bootstrap/react-table-defaults.tsx @@ -24,37 +24,39 @@ import { booleanCustomTableFilter, countBy, makeTextFilter } from '../utils'; import { ReactTableCustomPagination } from './react-table-custom-pagination'; -export const NoData = React.memo(function NoData(props) { +const NoData = React.memo(function NoData(props) { const { children } = props; if (!children) return null; return
{children}
; }); -Object.assign(ReactTableDefaults, { - className: '-striped -highlight', - defaultFilterMethod: (filter: Filter, row: any) => { - const id = filter.pivotId || filter.id; - return booleanCustomTableFilter(filter, row[id]); - }, - LoadingComponent: Loader, - loadingText: '', - NoDataComponent: NoData, - FilterComponent: makeTextFilter(), - PaginationComponent: ReactTableCustomPagination, - AggregatedComponent: (opt: any) => { - const { subRows, column } = opt; - const previewValues = subRows - .filter((d: any) => typeof d[column.id] !== 'undefined') - .map((row: any) => row[column.id]); - const previewCount = countBy(previewValues); - return ( - - {Object.keys(previewCount) - .sort() - .map(v => `${v} (${previewCount[v]})`) - .join(', ')} - - ); - }, - defaultPageSize: 20, -}); +export function bootstrapReactTable() { + Object.assign(ReactTableDefaults, { + className: '-striped -highlight', + defaultFilterMethod: (filter: Filter, row: any) => { + const id = filter.pivotId || filter.id; + return booleanCustomTableFilter(filter, row[id]); + }, + LoadingComponent: Loader, + loadingText: '', + NoDataComponent: NoData, + FilterComponent: makeTextFilter(), + PaginationComponent: ReactTableCustomPagination, + AggregatedComponent: (opt: any) => { + const { subRows, column } = opt; + const previewValues = subRows + .filter((d: any) => typeof d[column.id] !== 'undefined') + .map((row: any) => row[column.id]); + const previewCount = countBy(previewValues); + return ( + + {Object.keys(previewCount) + .sort() + .map(v => `${v} (${previewCount[v]})`) + .join(', ')} + + ); + }, + defaultPageSize: 20, + }); +} 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 fc840320473..a5ca3dcb5a9 100644 --- a/web-console/src/components/header-bar/header-bar.spec.tsx +++ b/web-console/src/components/header-bar/header-bar.spec.tsx @@ -19,7 +19,7 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { Capabilities } from '../../utils/capabilities'; +import { Capabilities } from '../../utils'; import { HeaderBar } from './header-bar'; diff --git a/web-console/src/components/header-bar/header-bar.tsx b/web-console/src/components/header-bar/header-bar.tsx index 01e48a88162..76bb378fe78 100644 --- a/web-console/src/components/header-bar/header-bar.tsx +++ b/web-console/src/components/header-bar/header-bar.tsx @@ -39,7 +39,7 @@ import { OverlordDynamicConfigDialog, } from '../../dialogs'; import { getLink } from '../../links'; -import { Capabilities } from '../../utils/capabilities'; +import { Capabilities } from '../../utils'; import { ExternalLink } from '../external-link/external-link'; import { PopoverText } from '../popover-text/popover-text'; diff --git a/web-console/src/components/segment-timeline/segment-timeline.spec.tsx b/web-console/src/components/segment-timeline/segment-timeline.spec.tsx index 83440d31461..1a9e8f9252b 100644 --- a/web-console/src/components/segment-timeline/segment-timeline.spec.tsx +++ b/web-console/src/components/segment-timeline/segment-timeline.spec.tsx @@ -21,7 +21,7 @@ import { mount } from 'enzyme'; import React from 'react'; import { QueryManager } from '../../utils'; -import { Capabilities } from '../../utils/capabilities'; +import { Capabilities } from '../../utils'; import { SegmentTimeline } from './segment-timeline'; diff --git a/web-console/src/components/segment-timeline/segment-timeline.tsx b/web-console/src/components/segment-timeline/segment-timeline.tsx index b928d15528d..4ccddcba648 100644 --- a/web-console/src/components/segment-timeline/segment-timeline.tsx +++ b/web-console/src/components/segment-timeline/segment-timeline.tsx @@ -17,12 +17,12 @@ */ import { FormGroup, HTMLSelect, Radio, RadioGroup } from '@blueprintjs/core'; -import axios from 'axios'; import { AxisScale } from 'd3-axis'; import { scaleLinear, scaleTime } from 'd3-scale'; import React from 'react'; -import { Capabilities } from '../../utils/capabilities'; +import { Api } from '../../singletons'; +import { Capabilities } from '../../utils'; import { formatBytes, queryDruidSql, QueryManager, uniq } from '../../utils/index'; import { StackedBarChart } from '../../visualization/stacked-bar-chart'; import { Loader } from '../loader/loader'; @@ -266,10 +266,10 @@ ORDER BY "start" DESC`; before.setMonth(before.getMonth() - timeSpan); const beforeIso = before.toISOString(); - datasources = (await axios.get(`/druid/coordinator/v1/datasources`)).data; + datasources = (await Api.instance.get(`/druid/coordinator/v1/datasources`)).data; intervals = (await Promise.all( datasources.map(async datasource => { - const intervalMap = (await axios.get( + const intervalMap = (await Api.instance.get( `/druid/coordinator/v1/datasources/${datasource}/intervals?simple`, )).data; diff --git a/web-console/src/components/show-history/show-history.tsx b/web-console/src/components/show-history/show-history.tsx index 9f55d299a92..a6086e66117 100644 --- a/web-console/src/components/show-history/show-history.tsx +++ b/web-console/src/components/show-history/show-history.tsx @@ -17,10 +17,10 @@ */ import { Tab, Tabs } from '@blueprintjs/core'; -import axios from 'axios'; import React from 'react'; import { useQueryManager } from '../../hooks'; +import { Api } from '../../singletons'; import { Loader } from '../loader/loader'; import { ShowValue } from '../show-value/show-value'; @@ -41,7 +41,7 @@ export const ShowHistory = React.memo(function ShowHistory(props: ShowHistoryPro const [historyState] = useQueryManager({ processQuery: async (endpoint: string) => { - const resp = await axios.get(endpoint); + const resp = await Api.instance.get(endpoint); return resp.data; }, initQuery: endpoint, diff --git a/web-console/src/components/show-json/show-json.tsx b/web-console/src/components/show-json/show-json.tsx index c178afcce84..726fc1fac25 100644 --- a/web-console/src/components/show-json/show-json.tsx +++ b/web-console/src/components/show-json/show-json.tsx @@ -17,13 +17,11 @@ */ import { Button, ButtonGroup, Intent, TextArea } from '@blueprintjs/core'; -import axios from 'axios'; import copy from 'copy-to-clipboard'; import React from 'react'; import { useQueryManager } from '../../hooks'; -import { AppToaster } from '../../singletons/toaster'; -import { UrlBaser } from '../../singletons/url-baser'; +import { Api, AppToaster, UrlBaser } from '../../singletons'; import { downloadFile } from '../../utils'; import { Loader } from '../loader/loader'; @@ -40,7 +38,7 @@ export const ShowJson = React.memo(function ShowJson(props: ShowJsonProps) { const [jsonState] = useQueryManager({ processQuery: async () => { - const resp = await axios.get(endpoint); + const resp = await Api.instance.get(endpoint); let data = resp.data; if (transform) data = transform(data); return typeof data === 'string' ? data : JSON.stringify(data, undefined, 2); diff --git a/web-console/src/components/show-log/show-log.tsx b/web-console/src/components/show-log/show-log.tsx index a4409d2c464..e9d248cc4e7 100644 --- a/web-console/src/components/show-log/show-log.tsx +++ b/web-console/src/components/show-log/show-log.tsx @@ -17,13 +17,11 @@ */ import { AnchorButton, Button, ButtonGroup, Intent, Switch } from '@blueprintjs/core'; -import axios from 'axios'; import copy from 'copy-to-clipboard'; import React from 'react'; import { Loader } from '../../components'; -import { AppToaster } from '../../singletons/toaster'; -import { UrlBaser } from '../../singletons/url-baser'; +import { Api, AppToaster, UrlBaser } from '../../singletons'; import { QueryManager, QueryState } from '../../utils'; import './show-log.scss'; @@ -65,7 +63,9 @@ export class ShowLog extends React.PureComponent { this.showLogQueryManager = new QueryManager({ processQuery: async () => { const { endpoint, tailOffset } = this.props; - const resp = await axios.get(endpoint + (tailOffset ? `?offset=-${tailOffset}` : '')); + const resp = await Api.instance.get( + endpoint + (tailOffset ? `?offset=-${tailOffset}` : ''), + ); const data = resp.data; let logValue = typeof data === 'string' ? data : JSON.stringify(data, undefined, 2); diff --git a/web-console/src/components/show-value/show-value.tsx b/web-console/src/components/show-value/show-value.tsx index 59afd2f07de..d41bdb52852 100644 --- a/web-console/src/components/show-value/show-value.tsx +++ b/web-console/src/components/show-value/show-value.tsx @@ -19,7 +19,7 @@ import { Button, ButtonGroup, TextArea } from '@blueprintjs/core'; import React from 'react'; -import { UrlBaser } from '../../singletons/url-baser'; +import { UrlBaser } from '../../singletons'; import { downloadFile } from '../../utils'; import './show-value.scss'; diff --git a/web-console/src/components/supervisor-statistics-table/supervisor-statistics-table.tsx b/web-console/src/components/supervisor-statistics-table/supervisor-statistics-table.tsx index a8c5383bc08..f7514a64189 100644 --- a/web-console/src/components/supervisor-statistics-table/supervisor-statistics-table.tsx +++ b/web-console/src/components/supervisor-statistics-table/supervisor-statistics-table.tsx @@ -17,12 +17,11 @@ */ import { Button, ButtonGroup } from '@blueprintjs/core'; -import axios from 'axios'; import React from 'react'; import ReactTable, { CellInfo, Column } from 'react-table'; import { useQueryManager } from '../../hooks'; -import { UrlBaser } from '../../singletons/url-baser'; +import { Api, UrlBaser } from '../../singletons'; import { deepGet } from '../../utils'; import { Loader } from '../loader/loader'; @@ -65,7 +64,7 @@ export const SupervisorStatisticsTable = React.memo(function SupervisorStatistic const [supervisorStatisticsState] = useQueryManager({ processQuery: async () => { - const resp = await axios.get(endpoint); + const resp = await Api.instance.get(endpoint); return normalizeSupervisorStatisticsResults(resp.data); }, initQuery: null, diff --git a/web-console/src/console-application.tsx b/web-console/src/console-application.tsx index c09100a6a77..84d8a3385ac 100644 --- a/web-console/src/console-application.tsx +++ b/web-console/src/console-application.tsx @@ -23,9 +23,8 @@ import React from 'react'; import { HashRouter, Route, Switch } from 'react-router-dom'; import { HeaderActiveTab, HeaderBar, Loader } from './components'; -import { AppToaster } from './singletons/toaster'; -import { QueryManager } from './utils'; -import { Capabilities } from './utils/capabilities'; +import { AppToaster } from './singletons'; +import { Capabilities, QueryManager } from './utils'; import { DatasourcesView, HomeView, diff --git a/web-console/src/dialogs/async-action-dialog/async-action-dialog.tsx b/web-console/src/dialogs/async-action-dialog/async-action-dialog.tsx index 7e07d467a70..ada4620e58f 100644 --- a/web-console/src/dialogs/async-action-dialog/async-action-dialog.tsx +++ b/web-console/src/dialogs/async-action-dialog/async-action-dialog.tsx @@ -31,7 +31,7 @@ import classNames from 'classnames'; import React, { ReactNode, useState } from 'react'; import { WarningChecklist } from '../../components/warning-checklist/warning-checklist'; -import { AppToaster } from '../../singletons/toaster'; +import { AppToaster } from '../../singletons'; import './async-action-dialog.scss'; diff --git a/web-console/src/dialogs/coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog.tsx b/web-console/src/dialogs/coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog.tsx index 0ec905463a1..83538dd82c1 100644 --- a/web-console/src/dialogs/coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog.tsx +++ b/web-console/src/dialogs/coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog.tsx @@ -18,7 +18,6 @@ import { Intent } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import axios from 'axios'; import React, { useState } from 'react'; import { SnitchDialog } from '..'; @@ -32,7 +31,7 @@ import { import { COORDINATOR_DYNAMIC_CONFIG_FIELDS, CoordinatorDynamicConfig } from '../../druid-models'; import { useQueryManager } from '../../hooks'; import { getLink } from '../../links'; -import { AppToaster } from '../../singletons/toaster'; +import { Api, AppToaster } from '../../singletons'; import { getDruidErrorMessage } from '../../utils'; import './coordinator-dynamic-config-dialog.scss'; @@ -50,7 +49,7 @@ export const CoordinatorDynamicConfigDialog = React.memo(function CoordinatorDyn const [historyRecordsState] = useQueryManager({ processQuery: async () => { - const historyResp = await axios(`/druid/coordinator/v1/config/history?count=100`); + const historyResp = await Api.instance.get(`/druid/coordinator/v1/config/history?count=100`); return historyResp.data; }, initQuery: null, @@ -59,7 +58,7 @@ export const CoordinatorDynamicConfigDialog = React.memo(function CoordinatorDyn useQueryManager>({ processQuery: async () => { try { - const configResp = await axios.get('/druid/coordinator/v1/config'); + const configResp = await Api.instance.get('/druid/coordinator/v1/config'); setDynamicConfig(configResp.data || {}); } catch (e) { AppToaster.show({ @@ -77,7 +76,7 @@ export const CoordinatorDynamicConfigDialog = React.memo(function CoordinatorDyn async function saveConfig(comment: string) { try { - await axios.post('/druid/coordinator/v1/config', dynamicConfig, { + await Api.instance.post('/druid/coordinator/v1/config', dynamicConfig, { headers: { 'X-Druid-Author': 'console', 'X-Druid-Comment': comment, diff --git a/web-console/src/dialogs/doctor-dialog/doctor-checks.tsx b/web-console/src/dialogs/doctor-dialog/doctor-checks.tsx index a5ecb853616..4fea19b9b3b 100644 --- a/web-console/src/dialogs/doctor-dialog/doctor-checks.tsx +++ b/web-console/src/dialogs/doctor-dialog/doctor-checks.tsx @@ -16,8 +16,7 @@ * limitations under the License. */ -import axios from 'axios'; - +import { Api } from '../../singletons'; import { deepGet, pluralIfNeeded, queryDruidSql } from '../../utils'; import { postToSampler } from '../../utils/sampler'; @@ -56,7 +55,7 @@ export const DOCTOR_CHECKS: DoctorCheck[] = [ // Make sure that the router responds to /status and gives some valid info back let status: any; try { - status = (await axios.get(`/status`)).data; + status = (await Api.instance.get(`/status`)).data; } catch (e) { controls.addIssue( `Did not get a /status response from the Router service. Try confirming that it is running and accessible. Got: ${e.message}`, @@ -76,7 +75,7 @@ export const DOCTOR_CHECKS: DoctorCheck[] = [ // Make sure that everything in /status/properties is above board let properties: Record; try { - properties = (await axios.get(`/status/properties`)).data; + properties = (await Api.instance.get(`/status/properties`)).data; } catch (e) { controls.addIssue( `Did not get a /status/properties response from the Router. Message: ${e.message}`, @@ -126,14 +125,14 @@ export const DOCTOR_CHECKS: DoctorCheck[] = [ // Make sure that everything in Coordinator's /status is good let myStatus: any; try { - myStatus = (await axios.get(`/status`)).data; + myStatus = (await Api.instance.get(`/status`)).data; } catch { return; } let coordinatorStatus: any; try { - coordinatorStatus = (await axios.get(`/proxy/coordinator/status`)).data; + coordinatorStatus = (await Api.instance.get(`/proxy/coordinator/status`)).data; } catch (e) { controls.addIssue( 'Did not get a /status response from the Coordinator service. Try confirming that it is running and accessible.', @@ -143,7 +142,7 @@ export const DOCTOR_CHECKS: DoctorCheck[] = [ let overlordStatus: any; try { - overlordStatus = (await axios.get(`/proxy/overlord/status`)).data; + overlordStatus = (await Api.instance.get(`/proxy/overlord/status`)).data; } catch (e) { controls.addIssue( 'Did not get a /status response from the Overlord service. Try confirming that it is running and accessible.', @@ -170,14 +169,15 @@ export const DOCTOR_CHECKS: DoctorCheck[] = [ // Make sure that everything in coordinator and overlord /status/properties is good and matches where needed let myProperties: Record; try { - myProperties = (await axios.get(`/status/properties`)).data; + myProperties = (await Api.instance.get(`/status/properties`)).data; } catch { return; } let coordinatorProperties: Record; try { - coordinatorProperties = (await axios.get(`/proxy/coordinator/status/properties`)).data; + coordinatorProperties = (await Api.instance.get(`/proxy/coordinator/status/properties`)) + .data; } catch (e) { controls.addIssue( 'Did not get a /status response from the coordinator. Try confirming that it is running and accessible.', @@ -187,7 +187,7 @@ export const DOCTOR_CHECKS: DoctorCheck[] = [ let overlordProperties: Record; try { - overlordProperties = (await axios.get(`/proxy/overlord/status/properties`)).data; + overlordProperties = (await Api.instance.get(`/proxy/overlord/status/properties`)).data; } catch (e) { controls.addIssue( 'Did not get a /status response from the overlord. Try confirming that it is running and accessible.', @@ -384,7 +384,8 @@ ORDER BY "num_bad_time_chunks"`, // Grab the auto-compaction definitions and ignore dataSources that already have auto-compaction let compactionResult: any; try { - compactionResult = (await axios.get('/druid/coordinator/v1/config/compaction')).data; + compactionResult = (await Api.instance.get('/druid/coordinator/v1/config/compaction')) + .data; } catch (e) { controls.addIssue(`Could not get compaction config. Something is wrong.`); return; diff --git a/web-console/src/dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog.tsx b/web-console/src/dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog.tsx index d54cba60521..81fcafd3b45 100644 --- a/web-console/src/dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog.tsx +++ b/web-console/src/dialogs/overlord-dynamic-config-dialog/overlord-dynamic-config-dialog.tsx @@ -18,7 +18,6 @@ import { Intent } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import axios from 'axios'; import React, { useState } from 'react'; import { SnitchDialog } from '..'; @@ -26,7 +25,7 @@ import { AutoForm, ExternalLink } from '../../components'; import { OVERLORD_DYNAMIC_CONFIG_FIELDS, OverlordDynamicConfig } from '../../druid-models'; import { useQueryManager } from '../../hooks'; import { getLink } from '../../links'; -import { AppToaster } from '../../singletons/toaster'; +import { Api, AppToaster } from '../../singletons'; import { getDruidErrorMessage } from '../../utils'; import './overlord-dynamic-config-dialog.scss'; @@ -43,7 +42,7 @@ export const OverlordDynamicConfigDialog = React.memo(function OverlordDynamicCo const [historyRecordsState] = useQueryManager({ processQuery: async () => { - const historyResp = await axios(`/druid/indexer/v1/worker/history?count=100`); + const historyResp = await Api.instance.get(`/druid/indexer/v1/worker/history?count=100`); return historyResp.data; }, initQuery: null, @@ -52,7 +51,7 @@ export const OverlordDynamicConfigDialog = React.memo(function OverlordDynamicCo useQueryManager>({ processQuery: async () => { try { - const configResp = await axios(`/druid/indexer/v1/worker`); + const configResp = await Api.instance.get(`/druid/indexer/v1/worker`); setDynamicConfig(configResp.data || {}); } catch (e) { AppToaster.show({ @@ -70,7 +69,7 @@ export const OverlordDynamicConfigDialog = React.memo(function OverlordDynamicCo async function saveConfig(comment: string) { try { - await axios.post('/druid/indexer/v1/worker', dynamicConfig, { + await Api.instance.post('/druid/indexer/v1/worker', dynamicConfig, { headers: { 'X-Druid-Author': 'console', 'X-Druid-Comment': comment, diff --git a/web-console/src/dialogs/retention-dialog/retention-dialog.tsx b/web-console/src/dialogs/retention-dialog/retention-dialog.tsx index 4e9819540b1..c11d345a759 100644 --- a/web-console/src/dialogs/retention-dialog/retention-dialog.tsx +++ b/web-console/src/dialogs/retention-dialog/retention-dialog.tsx @@ -18,13 +18,13 @@ import { Button, Divider, FormGroup } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import axios from 'axios'; import React, { useState } from 'react'; import { SnitchDialog } from '..'; import { ExternalLink, RuleEditor } from '../../components'; import { useQueryManager } from '../../hooks'; import { getLink } from '../../links'; +import { Api } from '../../singletons'; import { swapElements } from '../../utils'; import { Rule, RuleUtil } from '../../utils/load-rule'; @@ -46,7 +46,9 @@ export const RetentionDialog = React.memo(function RetentionDialog(props: Retent const [historyQueryState] = useQueryManager({ processQuery: async datasource => { - const historyResp = await axios(`/druid/coordinator/v1/rules/${datasource}/history`); + const historyResp = await Api.instance.get( + `/druid/coordinator/v1/rules/${datasource}/history`, + ); return historyResp.data; }, initQuery: props.datasource, diff --git a/web-console/src/dialogs/show-value-dialog/show-value-dialog.tsx b/web-console/src/dialogs/show-value-dialog/show-value-dialog.tsx index 504c7a75e4f..e6adc16c0f7 100644 --- a/web-console/src/dialogs/show-value-dialog/show-value-dialog.tsx +++ b/web-console/src/dialogs/show-value-dialog/show-value-dialog.tsx @@ -21,7 +21,7 @@ import { IconNames } from '@blueprintjs/icons'; import copy from 'copy-to-clipboard'; import React from 'react'; -import { AppToaster } from '../../singletons/toaster'; +import { AppToaster } from '../../singletons'; import './show-value-dialog.scss'; diff --git a/web-console/src/dialogs/status-dialog/status-dialog.tsx b/web-console/src/dialogs/status-dialog/status-dialog.tsx index 5d6aac55572..356895b6e82 100644 --- a/web-console/src/dialogs/status-dialog/status-dialog.tsx +++ b/web-console/src/dialogs/status-dialog/status-dialog.tsx @@ -17,13 +17,12 @@ */ import { Button, Classes, Dialog, Intent } from '@blueprintjs/core'; -import axios from 'axios'; import React from 'react'; import ReactTable, { Filter } from 'react-table'; import { Loader } from '../../components'; import { useQueryManager } from '../../hooks'; -import { UrlBaser } from '../../singletons/url-baser'; +import { Api, UrlBaser } from '../../singletons'; import './status-dialog.scss'; @@ -50,7 +49,7 @@ export const StatusDialog = React.memo(function StatusDialog(props: StatusDialog const { onClose } = props; const [responseState] = useQueryManager({ processQuery: async () => { - const resp = await axios.get(`/status`); + const resp = await Api.instance.get(`/status`); return resp.data; }, initQuery: null, diff --git a/web-console/src/entry.ts b/web-console/src/entry.ts index bebb5ff71bd..45fe09364b9 100644 --- a/web-console/src/entry.ts +++ b/web-console/src/entry.ts @@ -16,24 +16,22 @@ * limitations under the License. */ -import axios from 'axios'; -import 'brace'; // Import Ace editor and all the sub components used in the app -import 'brace/ext/language_tools'; -import 'brace/theme/solarized_dark'; +import { AxiosRequestConfig } from 'axios'; import 'core-js/stable'; import React from 'react'; import ReactDOM from 'react-dom'; import 'regenerator-runtime/runtime'; -import './ace-modes/dsql'; -import './ace-modes/hjson'; -import './bootstrap/react-table-defaults'; +import './bootstrap/ace'; +import { bootstrapReactTable } from './bootstrap/react-table-defaults'; import { ConsoleApplication } from './console-application'; import { Links, setLinkOverrides } from './links'; -import { UrlBaser } from './singletons/url-baser'; +import { Api, UrlBaser } from './singletons'; import './entry.scss'; +bootstrapReactTable(); + const container = document.getElementsByClassName('app-container')[0]; if (!container) throw new Error('container not found'); @@ -69,16 +67,23 @@ if (typeof consoleConfig.title === 'string') { window.document.title = consoleConfig.title; } +const apiConfig: AxiosRequestConfig = { + headers: {}, +}; + if (consoleConfig.baseURL) { - axios.defaults.baseURL = consoleConfig.baseURL; + apiConfig.baseURL = consoleConfig.baseURL; UrlBaser.baseUrl = consoleConfig.baseURL; } if (consoleConfig.customHeaderName && consoleConfig.customHeaderValue) { - axios.defaults.headers.common[consoleConfig.customHeaderName] = consoleConfig.customHeaderValue; + apiConfig.headers.common[consoleConfig.customHeaderName] = consoleConfig.customHeaderValue; } if (consoleConfig.customHeaders) { - Object.assign(axios.defaults.headers, consoleConfig.customHeaders); + Object.assign(apiConfig.headers, consoleConfig.customHeaders); } + +Api.initialize(apiConfig); + if (consoleConfig.linkOverrides) { setLinkOverrides(consoleConfig.linkOverrides); } diff --git a/web-console/src/setup-tests.ts b/web-console/src/setup-tests.ts index 9f60d0444f8..e52845b6018 100644 --- a/web-console/src/setup-tests.ts +++ b/web-console/src/setup-tests.ts @@ -20,7 +20,7 @@ import 'core-js/stable'; import { configure } from 'enzyme'; import enzymeAdapterReact16 from 'enzyme-adapter-react-16'; -import { UrlBaser } from './singletons/url-baser'; +import { UrlBaser } from './singletons'; configure({ adapter: new (enzymeAdapterReact16 as any)() }); diff --git a/web-console/src/singletons/api.ts b/web-console/src/singletons/api.ts new file mode 100644 index 00000000000..1e8f53750d3 --- /dev/null +++ b/web-console/src/singletons/api.ts @@ -0,0 +1,27 @@ +/* + * 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. + */ + +import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'; + +export class Api { + static instance: AxiosInstance; + + static initialize(config?: AxiosRequestConfig): void { + Api.instance = axios.create(config); + } +} diff --git a/web-console/src/singletons/index.ts b/web-console/src/singletons/index.ts new file mode 100644 index 00000000000..bf21dfcfe8a --- /dev/null +++ b/web-console/src/singletons/index.ts @@ -0,0 +1,21 @@ +/* + * 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 * from './api'; +export * from './toaster'; +export * from './url-baser'; diff --git a/web-console/src/utils/capabilities.ts b/web-console/src/utils/capabilities.ts index 738c3a70787..96c01ab5f11 100644 --- a/web-console/src/utils/capabilities.ts +++ b/web-console/src/utils/capabilities.ts @@ -16,7 +16,7 @@ * limitations under the License. */ -import axios from 'axios'; +import { Api } from '../singletons'; import { localStorageGetJson, LocalStorageKeys } from './local-storage-keys'; @@ -51,7 +51,7 @@ export class Capabilities { static async detectQueryType(): Promise { // Check SQL endpoint try { - await axios.post( + await Api.instance.post( '/druid/v2/sql', { query: 'SELECT 1337', context: { timeout: Capabilities.STATUS_TIMEOUT } }, { timeout: Capabilities.STATUS_TIMEOUT }, @@ -62,14 +62,14 @@ export class Capabilities { return; // other failure } try { - await axios.get('/status', { timeout: Capabilities.STATUS_TIMEOUT }); + await Api.instance.get('/status', { timeout: Capabilities.STATUS_TIMEOUT }); } catch (e) { return; // total failure } // Status works but SQL 405s => the SQL endpoint is disabled try { - await axios.post( + await Api.instance.post( '/druid/v2', { queryType: 'dataSourceMetadata', @@ -94,7 +94,7 @@ export class Capabilities { static async detectNode(node: 'coordinator' | 'overlord'): Promise { try { - await axios.get(`/druid/${node === 'overlord' ? 'indexer' : node}/v1/isLeader`, { + await Api.instance.get(`/druid/${node === 'overlord' ? 'indexer' : node}/v1/isLeader`, { timeout: Capabilities.STATUS_TIMEOUT, }); } catch (e) { diff --git a/web-console/src/utils/druid-query.ts b/web-console/src/utils/druid-query.ts index d0865c1bad7..8940332ea79 100644 --- a/web-console/src/utils/druid-query.ts +++ b/web-console/src/utils/druid-query.ts @@ -16,8 +16,9 @@ * limitations under the License. */ -import axios from 'axios'; -import { AxiosResponse } from 'axios'; +import axios, { AxiosResponse } from 'axios'; + +import { Api } from '../singletons'; import { assemble } from './general'; import { RowColumn } from './query-cursor'; @@ -213,7 +214,7 @@ export class DruidError extends Error { export async function queryDruidRune(runeQuery: Record): Promise { let runeResultResp: AxiosResponse; try { - runeResultResp = await axios.post('/druid/v2', runeQuery); + runeResultResp = await Api.instance.post('/druid/v2', runeQuery); } catch (e) { throw new Error(getDruidErrorMessage(e)); } @@ -223,7 +224,7 @@ export async function queryDruidRune(runeQuery: Record): Promise(sqlQueryPayload: Record): Promise { let sqlResultResp: AxiosResponse; try { - sqlResultResp = await axios.post('/druid/v2/sql', sqlQueryPayload); + sqlResultResp = await Api.instance.post('/druid/v2/sql', sqlQueryPayload); } catch (e) { throw new Error(getDruidErrorMessage(e)); } diff --git a/web-console/src/utils/general.tsx b/web-console/src/utils/general.tsx index 9ce6e6186e8..8783f084841 100644 --- a/web-console/src/utils/general.tsx +++ b/web-console/src/utils/general.tsx @@ -26,7 +26,7 @@ import numeral from 'numeral'; import React from 'react'; import { Filter, FilterRender } from 'react-table'; -import { AppToaster } from '../singletons/toaster'; +import { AppToaster } from '../singletons'; // These constants are used to make sure that they are not constantly recreated thrashing the pure components export const EMPTY_OBJECT: any = {}; diff --git a/web-console/src/utils/sampler.ts b/web-console/src/utils/sampler.ts index 4a888600b84..5f8b97eb34e 100644 --- a/web-console/src/utils/sampler.ts +++ b/web-console/src/utils/sampler.ts @@ -16,8 +16,6 @@ * limitations under the License. */ -import axios from 'axios'; - import { DimensionsSpec, getSpecType, @@ -34,6 +32,7 @@ import { TransformSpec, upgradeSpec, } from '../druid-models'; +import { Api } from '../singletons'; import { getDruidErrorMessage, queryDruidRune } from './druid-query'; import { @@ -181,7 +180,7 @@ export function headerAndRowsFromSampleResponse( export async function getProxyOverlordModules(): Promise { let statusResp: any; try { - statusResp = await axios.get(`/proxy/overlord/status`); + statusResp = await Api.instance.get(`/proxy/overlord/status`); } catch (e) { throw new Error(getDruidErrorMessage(e)); } @@ -197,7 +196,7 @@ export async function postToSampler( let sampleResp: any; try { - sampleResp = await axios.post(`${SAMPLER_URL}?for=${forStr}`, sampleSpec); + sampleResp = await Api.instance.post(`${SAMPLER_URL}?for=${forStr}`, sampleSpec); } catch (e) { throw new Error(getDruidErrorMessage(e)); } diff --git a/web-console/src/views/datasource-view/datasource-view.spec.tsx b/web-console/src/views/datasource-view/datasource-view.spec.tsx index 138337dc49e..952d51ffffa 100644 --- a/web-console/src/views/datasource-view/datasource-view.spec.tsx +++ b/web-console/src/views/datasource-view/datasource-view.spec.tsx @@ -19,7 +19,7 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { Capabilities } from '../../utils/capabilities'; +import { Capabilities } from '../../utils'; import { DatasourcesView } from './datasource-view'; diff --git a/web-console/src/views/datasource-view/datasource-view.tsx b/web-console/src/views/datasource-view/datasource-view.tsx index 8b7e7f8bc88..678f98487f4 100644 --- a/web-console/src/views/datasource-view/datasource-view.tsx +++ b/web-console/src/views/datasource-view/datasource-view.tsx @@ -18,7 +18,6 @@ import { FormGroup, InputGroup, Intent, MenuItem, Switch } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import axios from 'axios'; import classNames from 'classnames'; import { SqlQuery, SqlRef } from 'druid-query-toolkit'; import React from 'react'; @@ -45,7 +44,7 @@ import { formatCompactionConfigAndStatus, zeroCompactionStatus, } from '../../druid-models'; -import { AppToaster } from '../../singletons/toaster'; +import { Api, AppToaster } from '../../singletons'; import { addFilter, Capabilities, @@ -317,8 +316,10 @@ GROUP BY 1`; if (capabilities.hasSql()) { datasources = await queryDruidSql({ query: DatasourcesView.DATASOURCE_SQL }); } else if (capabilities.hasCoordinatorAccess()) { - const datasourcesResp = await axios.get('/druid/coordinator/v1/datasources?simple'); - const loadstatusResp = await axios.get('/druid/coordinator/v1/loadstatus?simple'); + const datasourcesResp = await Api.instance.get( + '/druid/coordinator/v1/datasources?simple', + ); + const loadstatusResp = await Api.instance.get('/druid/coordinator/v1/loadstatus?simple'); const loadstatus = loadstatusResp.data; datasources = datasourcesResp.data.map( (d: any): DatasourceQueryResultRow => { @@ -367,22 +368,26 @@ GROUP BY 1`; if (this.state.showUnused) { // Using 'includeDisabled' parameter for compatibility. // Should be changed to 'includeUnused' in Druid 0.17 - const unusedResp = await axios.get( + const unusedResp = await Api.instance.get( '/druid/coordinator/v1/metadata/datasources?includeDisabled', ); unused = unusedResp.data.filter((d: string) => !seen[d]); } - const rulesResp = await axios.get('/druid/coordinator/v1/rules'); + const rulesResp = await Api.instance.get('/druid/coordinator/v1/rules'); const rules = rulesResp.data; - const compactionConfigsResp = await axios.get('/druid/coordinator/v1/config/compaction'); + const compactionConfigsResp = await Api.instance.get( + '/druid/coordinator/v1/config/compaction', + ); const compactionConfigs = lookupBy( compactionConfigsResp.data.compactionConfigs || [], (c: CompactionConfig) => c.dataSource, ); - const compactionStatusesResp = await axios.get('/druid/coordinator/v1/compaction/status'); + const compactionStatusesResp = await Api.instance.get( + '/druid/coordinator/v1/compaction/status', + ); const compactionStatuses = lookupBy( compactionStatusesResp.data.latestStatus || [], (c: CompactionStatus) => c.dataSource, @@ -412,7 +417,7 @@ GROUP BY 1`; this.tiersQueryManager = new QueryManager({ processQuery: async capabilities => { if (capabilities.hasCoordinatorAccess()) { - const tiersResp = await axios.get('/druid/coordinator/v1/tiers'); + const tiersResp = await Api.instance.get('/druid/coordinator/v1/tiers'); return tiersResp.data; } else { throw new Error(`must have coordinator access`); @@ -455,7 +460,7 @@ GROUP BY 1`; return ( { - const resp = await axios.delete( + const resp = await Api.instance.delete( `/druid/coordinator/v1/datasources/${datasourceToMarkAsUnusedAllSegmentsIn}`, {}, ); @@ -486,7 +491,7 @@ GROUP BY 1`; return ( { - const resp = await axios.post( + const resp = await Api.instance.post( `/druid/coordinator/v1/datasources/${datasourceToMarkAllNonOvershadowedSegmentsAsUsedIn}`, {}, ); @@ -518,7 +523,7 @@ GROUP BY 1`; action={async () => { if (!useUnuseInterval) return; const param = isUse ? 'markUsed' : 'markUnused'; - const resp = await axios.post( + const resp = await Api.instance.post( `/druid/coordinator/v1/datasources/${datasourceToMarkSegmentsByIntervalIn}/${param}`, { interval: useUnuseInterval, @@ -560,7 +565,7 @@ GROUP BY 1`; return ( { - const resp = await axios.delete( + const resp = await Api.instance.delete( `/druid/coordinator/v1/datasources/${killDatasource}?kill=true&interval=1000/3000`, {}, ); @@ -628,7 +633,7 @@ GROUP BY 1`; return ( { - const resp = await axios.post(`/druid/coordinator/v1/compaction/compact`, {}); + const resp = await Api.instance.post(`/druid/coordinator/v1/compaction/compact`, {}); return resp.data; }} confirmButtonText="Force compaction run" @@ -648,7 +653,7 @@ GROUP BY 1`; private saveRules = async (datasource: string, rules: Rule[], comment: string) => { try { - await axios.post(`/druid/coordinator/v1/rules/${datasource}`, rules, { + await Api.instance.post(`/druid/coordinator/v1/rules/${datasource}`, rules, { headers: { 'X-Druid-Author': 'console', 'X-Druid-Comment': comment, @@ -689,7 +694,7 @@ GROUP BY 1`; private saveCompaction = async (compactionConfig: any) => { if (!compactionConfig) return; try { - await axios.post(`/druid/coordinator/v1/config/compaction`, compactionConfig); + await Api.instance.post(`/druid/coordinator/v1/config/compaction`, compactionConfig); this.setState({ compactionDialogOpenOn: undefined }); this.datasourceQueryManager.rerunLastQuery(); } catch (e) { @@ -711,7 +716,7 @@ GROUP BY 1`; text: 'Confirm', onClick: async () => { try { - await axios.delete(`/druid/coordinator/v1/config/compaction/${datasource}`); + await Api.instance.delete(`/druid/coordinator/v1/config/compaction/${datasource}`); this.setState({ compactionDialogOpenOn: undefined }, () => this.datasourceQueryManager.rerunLastQuery(), ); 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 9195c62ad12..b19a901a9bb 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,7 +19,7 @@ import { render } from '@testing-library/react'; import React from 'react'; -import { Capabilities } from '../../../utils/capabilities'; +import { Capabilities } from '../../../utils'; import { DatasourcesCard } from './datasources-card'; 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 c8ec31a3e95..dfcdefd5526 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 @@ -17,12 +17,12 @@ */ import { IconNames } from '@blueprintjs/icons'; -import axios from 'axios'; import React from 'react'; import { useQueryManager } from '../../../hooks'; +import { Api } from '../../../singletons'; import { pluralIfNeeded, queryDruidSql } from '../../../utils'; -import { Capabilities } from '../../../utils/capabilities'; +import { Capabilities } from '../../../utils'; import { HomeViewCard } from '../home-view-card/home-view-card'; export interface DatasourcesCardProps { @@ -38,7 +38,7 @@ export const DatasourcesCard = React.memo(function DatasourcesCard(props: Dataso query: `SELECT datasource FROM sys.segments GROUP BY 1`, }); } else if (capabilities.hasCoordinatorAccess()) { - const datasourcesResp = await axios.get('/druid/coordinator/v1/datasources'); + const datasourcesResp = await Api.instance.get('/druid/coordinator/v1/datasources'); datasources = datasourcesResp.data; } else { throw new Error(`must have SQL or coordinator access`); 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 a2820e890cc..65059ebbe10 100644 --- a/web-console/src/views/home-view/home-view.spec.tsx +++ b/web-console/src/views/home-view/home-view.spec.tsx @@ -19,7 +19,7 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { Capabilities } from '../../utils/capabilities'; +import { Capabilities } from '../../utils'; import { HomeView } from './home-view'; diff --git a/web-console/src/views/home-view/home-view.tsx b/web-console/src/views/home-view/home-view.tsx index 7a3279d5ed9..29295236daa 100644 --- a/web-console/src/views/home-view/home-view.tsx +++ b/web-console/src/views/home-view/home-view.tsx @@ -18,7 +18,7 @@ import React from 'react'; -import { Capabilities } from '../../utils/capabilities'; +import { Capabilities } from '../../utils'; import { DatasourcesCard } from './datasources-card/datasources-card'; import { LookupsCard } from './lookups-card/lookups-card'; diff --git a/web-console/src/views/home-view/lookups-card/lookups-card.spec.tsx b/web-console/src/views/home-view/lookups-card/lookups-card.spec.tsx index 2873afd511f..2f16cc0f9ed 100644 --- a/web-console/src/views/home-view/lookups-card/lookups-card.spec.tsx +++ b/web-console/src/views/home-view/lookups-card/lookups-card.spec.tsx @@ -19,7 +19,7 @@ import { render } from '@testing-library/react'; import React from 'react'; -import { Capabilities } from '../../../utils/capabilities'; +import { Capabilities } from '../../../utils'; import { LookupsCard } from './lookups-card'; diff --git a/web-console/src/views/home-view/lookups-card/lookups-card.tsx b/web-console/src/views/home-view/lookups-card/lookups-card.tsx index b752d577769..5cb2cfa4d7c 100644 --- a/web-console/src/views/home-view/lookups-card/lookups-card.tsx +++ b/web-console/src/views/home-view/lookups-card/lookups-card.tsx @@ -17,13 +17,13 @@ */ import { IconNames } from '@blueprintjs/icons'; -import axios from 'axios'; import { sum } from 'd3-array'; import React from 'react'; import { useQueryManager } from '../../../hooks'; +import { Api } from '../../../singletons'; import { isLookupsUninitialized, pluralIfNeeded } from '../../../utils'; -import { Capabilities } from '../../../utils/capabilities'; +import { Capabilities } from '../../../utils'; import { HomeViewCard } from '../home-view-card/home-view-card'; export interface LookupsCardProps { @@ -34,7 +34,7 @@ export const LookupsCard = React.memo(function LookupsCard(props: LookupsCardPro const [lookupsCountState] = useQueryManager({ processQuery: async capabilities => { if (capabilities.hasCoordinatorAccess()) { - const resp = await axios.get('/druid/coordinator/v1/lookups/status'); + const resp = await Api.instance.get('/druid/coordinator/v1/lookups/status'); const data = resp.data; return sum(Object.keys(data).map(k => Object.keys(data[k]).length)); } else { 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 e9988fcd4af..e157ee79dd7 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,7 +19,7 @@ import { render } from '@testing-library/react'; import React from 'react'; -import { Capabilities } from '../../../utils/capabilities'; +import { Capabilities } from '../../../utils'; import { SegmentsCard } from './segments-card'; 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 2245aae8d08..3eef75842e5 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 @@ -17,11 +17,11 @@ */ import { IconNames } from '@blueprintjs/icons'; -import axios from 'axios'; import { sum } from 'd3-array'; import React from 'react'; import { useQueryManager } from '../../../hooks'; +import { Api } from '../../../singletons'; import { Capabilities, deepGet, pluralIfNeeded, queryDruidSql } from '../../../utils'; import { HomeViewCard } from '../home-view-card/home-view-card'; @@ -46,11 +46,13 @@ 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 loadstatusResp = await Api.instance.get('/druid/coordinator/v1/loadstatus?simple'); const loadstatus = loadstatusResp.data; const unavailableSegmentNum = sum(Object.keys(loadstatus), key => loadstatus[key]); - const datasourcesMetaResp = await axios.get('/druid/coordinator/v1/datasources?simple'); + const datasourcesMetaResp = await Api.instance.get( + '/druid/coordinator/v1/datasources?simple', + ); const datasourcesMeta = datasourcesMetaResp.data; const availableSegmentNum = sum(datasourcesMeta, (curr: any) => deepGet(curr, 'properties.segments.count'), diff --git a/web-console/src/views/home-view/services-card/services-card.spec.tsx b/web-console/src/views/home-view/services-card/services-card.spec.tsx index dfec084662d..ec6fc20ff02 100644 --- a/web-console/src/views/home-view/services-card/services-card.spec.tsx +++ b/web-console/src/views/home-view/services-card/services-card.spec.tsx @@ -19,7 +19,7 @@ import { render } from '@testing-library/react'; import React from 'react'; -import { Capabilities } from '../../../utils/capabilities'; +import { Capabilities } from '../../../utils'; import { ServicesCard } from './services-card'; diff --git a/web-console/src/views/home-view/services-card/services-card.tsx b/web-console/src/views/home-view/services-card/services-card.tsx index 3ef5ec92cec..ddc184b2c63 100644 --- a/web-console/src/views/home-view/services-card/services-card.tsx +++ b/web-console/src/views/home-view/services-card/services-card.tsx @@ -17,13 +17,13 @@ */ import { IconNames } from '@blueprintjs/icons'; -import axios from 'axios'; import React from 'react'; import { PluralPairIfNeeded } from '../../../components/plural-pair-if-needed/plural-pair-if-needed'; import { useQueryManager } from '../../../hooks'; +import { Api } from '../../../singletons'; import { lookupBy, queryDruidSql } from '../../../utils'; -import { Capabilities } from '../../../utils/capabilities'; +import { Capabilities } from '../../../utils'; import { HomeViewCard } from '../home-view-card/home-view-card'; export interface ServiceCounts { @@ -53,10 +53,10 @@ export const ServicesCard = React.memo(function ServicesCard(props: ServicesCard }); return lookupBy(serviceCountsFromQuery, x => x.service_type, x => x.count); } else if (capabilities.hasCoordinatorAccess()) { - const services = (await axios.get('/druid/coordinator/v1/servers?simple')).data; + const services = (await Api.instance.get('/druid/coordinator/v1/servers?simple')).data; const middleManager = capabilities.hasOverlordAccess() - ? (await axios.get('/druid/indexer/v1/workers')).data + ? (await Api.instance.get('/druid/indexer/v1/workers')).data : []; return { diff --git a/web-console/src/views/home-view/status-card/status-card.tsx b/web-console/src/views/home-view/status-card/status-card.tsx index d5f3bc81193..09ea1f375d1 100644 --- a/web-console/src/views/home-view/status-card/status-card.tsx +++ b/web-console/src/views/home-view/status-card/status-card.tsx @@ -17,11 +17,11 @@ */ import { IconNames } from '@blueprintjs/icons'; -import axios from 'axios'; import React, { useState } from 'react'; import { StatusDialog } from '../../../dialogs/status-dialog/status-dialog'; import { useQueryManager } from '../../../hooks'; +import { Api } from '../../../singletons'; import { pluralIfNeeded } from '../../../utils'; import { HomeViewCard } from '../home-view-card/home-view-card'; @@ -36,7 +36,7 @@ export const StatusCard = React.memo(function StatusCard(_props: StatusCardProps const [showStatusDialog, setShowStatusDialog] = useState(false); const [statusSummaryState] = useQueryManager({ processQuery: async () => { - const statusResp = await axios.get('/status'); + const statusResp = await Api.instance.get('/status'); return { version: statusResp.data.version, extensionCount: statusResp.data.modules.length, 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 ec19bef5d2c..9d9625a8df0 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,7 +19,7 @@ import { render } from '@testing-library/react'; import React from 'react'; -import { Capabilities } from '../../../utils/capabilities'; +import { Capabilities } from '../../../utils'; import { SupervisorsCard } from './supervisors-card'; 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 ce81f87bc52..c11c1285ecc 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 @@ -17,12 +17,12 @@ */ import { IconNames } from '@blueprintjs/icons'; -import axios from 'axios'; import React from 'react'; import { useQueryManager } from '../../../hooks'; +import { Api } from '../../../singletons'; import { pluralIfNeeded, queryDruidSql } from '../../../utils'; -import { Capabilities } from '../../../utils/capabilities'; +import { Capabilities } from '../../../utils'; import { HomeViewCard } from '../home-view-card/home-view-card'; export interface SupervisorCounts { @@ -45,7 +45,7 @@ export const SupervisorsCard = React.memo(function SupervisorsCard(props: Superv FROM sys.supervisors`, }))[0]; } else if (capabilities.hasOverlordAccess()) { - const resp = await axios.get('/druid/indexer/v1/supervisor?full'); + const resp = await Api.instance.get('/druid/indexer/v1/supervisor?full'); const data = resp.data; return { running: data.filter((d: any) => d.spec.suspended === false).length, 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 3398b6a7c23..fd4c8fcc4fa 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,7 +19,7 @@ import { render } from '@testing-library/react'; import React from 'react'; -import { Capabilities } from '../../../utils/capabilities'; +import { Capabilities } from '../../../utils'; import { TasksCard } from './tasks-card'; 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 3687607ee5e..88fa6f30d00 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 @@ -17,13 +17,13 @@ */ import { IconNames } from '@blueprintjs/icons'; -import axios from 'axios'; import React from 'react'; import { PluralPairIfNeeded } from '../../../components/plural-pair-if-needed/plural-pair-if-needed'; import { useQueryManager } from '../../../hooks'; +import { Api } from '../../../singletons'; import { lookupBy, pluralIfNeeded, queryDruidSql } from '../../../utils'; -import { Capabilities } from '../../../utils/capabilities'; +import { Capabilities } from '../../../utils'; import { HomeViewCard } from '../home-view-card/home-view-card'; function getTaskStatus(d: any) { @@ -55,7 +55,7 @@ GROUP BY 1`, }); return lookupBy(taskCountsFromQuery, x => x.status, x => x.count); } else if (capabilities.hasOverlordAccess()) { - const tasks: any[] = (await axios.get('/druid/indexer/v1/tasks')).data; + const tasks: any[] = (await Api.instance.get('/druid/indexer/v1/tasks')).data; return { SUCCESS: tasks.filter(d => getTaskStatus(d) === 'SUCCESS').length, FAILED: tasks.filter(d => getTaskStatus(d) === 'FAILED').length, diff --git a/web-console/src/views/ingestion-view/ingestion-view.spec.tsx b/web-console/src/views/ingestion-view/ingestion-view.spec.tsx index ddbaa9d9103..17404b4f45b 100644 --- a/web-console/src/views/ingestion-view/ingestion-view.spec.tsx +++ b/web-console/src/views/ingestion-view/ingestion-view.spec.tsx @@ -19,7 +19,7 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { Capabilities } from '../../utils/capabilities'; +import { Capabilities } from '../../utils'; import { IngestionView } from './ingestion-view'; diff --git a/web-console/src/views/ingestion-view/ingestion-view.tsx b/web-console/src/views/ingestion-view/ingestion-view.tsx index d4826d8105d..4dd15dfc401 100644 --- a/web-console/src/views/ingestion-view/ingestion-view.tsx +++ b/web-console/src/views/ingestion-view/ingestion-view.tsx @@ -18,7 +18,6 @@ import { Alert, Button, ButtonGroup, Intent, Label, MenuItem } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import axios from 'axios'; import React from 'react'; import SplitterLayout from 'react-splitter-layout'; import ReactTable from 'react-table'; @@ -40,7 +39,7 @@ import { SupervisorTableActionDialog, TaskTableActionDialog, } from '../../dialogs'; -import { AppToaster } from '../../singletons/toaster'; +import { Api, AppToaster } from '../../singletons'; import { addFilter, addFilterRaw, @@ -56,8 +55,8 @@ import { QueryManager, QueryState, } from '../../utils'; +import { Capabilities } from '../../utils'; import { BasicAction } from '../../utils/basic-action'; -import { Capabilities } from '../../utils/capabilities'; import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array'; import './ingestion-view.scss'; @@ -251,7 +250,7 @@ ORDER BY "rank" DESC, "created_time" DESC`; query: IngestionView.SUPERVISOR_SQL, }); } else if (capabilities.hasOverlordAccess()) { - const supervisors = (await axios.get('/druid/indexer/v1/supervisor?full')).data; + const supervisors = (await Api.instance.get('/druid/indexer/v1/supervisor?full')).data; if (!Array.isArray(supervisors)) throw new Error(`Unexpected results`); return supervisors.map((sup: any) => { return { @@ -284,7 +283,7 @@ ORDER BY "rank" DESC, "created_time" DESC`; query: IngestionView.TASK_SQL, }); } else if (capabilities.hasOverlordAccess()) { - const resp = await axios.get(`/druid/indexer/v1/tasks`); + const resp = await Api.instance.get(`/druid/indexer/v1/tasks`); return IngestionView.parseTasks(resp.data); } else { throw new Error(`must have SQL or overlord access`); @@ -343,7 +342,7 @@ ORDER BY "rank" DESC, "created_time" DESC`; private submitSupervisor = async (spec: JSON) => { try { - await axios.post('/druid/indexer/v1/supervisor', spec); + await Api.instance.post('/druid/indexer/v1/supervisor', spec); } catch (e) { AppToaster.show({ message: `Failed to submit supervisor: ${getDruidErrorMessage(e)}`, @@ -361,7 +360,7 @@ ORDER BY "rank" DESC, "created_time" DESC`; private submitTask = async (spec: JSON) => { try { - await axios.post('/druid/indexer/v1/task', spec); + await Api.instance.post('/druid/indexer/v1/task', spec); } catch (e) { AppToaster.show({ message: `Failed to submit task: ${getDruidErrorMessage(e)}`, @@ -431,7 +430,7 @@ ORDER BY "rank" DESC, "created_time" DESC`; return ( { - const resp = await axios.post( + const resp = await Api.instance.post( `/druid/indexer/v1/supervisor/${resumeSupervisorId}/resume`, {}, ); @@ -460,7 +459,7 @@ ORDER BY "rank" DESC, "created_time" DESC`; return ( { - const resp = await axios.post( + const resp = await Api.instance.post( `/druid/indexer/v1/supervisor/${suspendSupervisorId}/suspend`, {}, ); @@ -489,7 +488,7 @@ ORDER BY "rank" DESC, "created_time" DESC`; return ( { - const resp = await axios.post( + const resp = await Api.instance.post( `/druid/indexer/v1/supervisor/${resetSupervisorId}/reset`, {}, ); @@ -527,7 +526,7 @@ ORDER BY "rank" DESC, "created_time" DESC`; return ( { - const resp = await axios.post( + const resp = await Api.instance.post( `/druid/indexer/v1/supervisor/${terminateSupervisorId}/terminate`, {}, ); @@ -684,7 +683,7 @@ ORDER BY "rank" DESC, "created_time" DESC`; return ( { - const resp = await axios.post(`/druid/indexer/v1/task/${killTaskId}/shutdown`, {}); + const resp = await Api.instance.post(`/druid/indexer/v1/task/${killTaskId}/shutdown`, {}); return resp.data; }} confirmButtonText="Kill task" @@ -945,7 +944,7 @@ ORDER BY "rank" DESC, "created_time" DESC`; return ( { - const resp = await axios.post(`/druid/indexer/v1/supervisor/resumeAll`, {}); + const resp = await Api.instance.post(`/druid/indexer/v1/supervisor/resumeAll`, {}); return resp.data; }} confirmButtonText="Resume all supervisors" @@ -971,7 +970,7 @@ ORDER BY "rank" DESC, "created_time" DESC`; return ( { - const resp = await axios.post(`/druid/indexer/v1/supervisor/suspendAll`, {}); + const resp = await Api.instance.post(`/druid/indexer/v1/supervisor/suspendAll`, {}); return resp.data; }} confirmButtonText="Suspend all supervisors" @@ -997,7 +996,7 @@ ORDER BY "rank" DESC, "created_time" DESC`; return ( { - const resp = await axios.post(`/druid/indexer/v1/supervisor/terminateAll`, {}); + const resp = await Api.instance.post(`/druid/indexer/v1/supervisor/terminateAll`, {}); return resp.data; }} confirmButtonText="Terminate all supervisors" 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 d13e17b95d3..75b427a3e7d 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 @@ -39,7 +39,6 @@ import { TextArea, } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import axios from 'axios'; import classNames from 'classnames'; import memoize from 'memoize-one'; import React from 'react'; @@ -121,8 +120,7 @@ import { upgradeSpec, } from '../../druid-models'; import { getLink } from '../../links'; -import { AppToaster } from '../../singletons/toaster'; -import { UrlBaser } from '../../singletons/url-baser'; +import { Api, AppToaster, UrlBaser } from '../../singletons'; import { deepDelete, deepGet, @@ -3220,7 +3218,7 @@ export class LoadDataView extends React.PureComponent { - const tiersResp = await axios.get('/druid/coordinator/v1/lookups/config?discover=true'); + const tiersResp = await Api.instance.get( + '/druid/coordinator/v1/lookups/config?discover=true', + ); const tiers = tiersResp.data && tiersResp.data.length > 0 ? tiersResp.data.sort(tierNameCompare) : [DEFAULT_LOOKUP_TIER]; const lookupEntries: {}[] = []; - const lookupResp = await axios.get('/druid/coordinator/v1/lookups/config/all'); + const lookupResp = await Api.instance.get('/druid/coordinator/v1/lookups/config/all'); const lookupData = lookupResp.data; Object.keys(lookupData).map((tier: string) => { const lookupIds = lookupData[tier]; @@ -151,7 +152,7 @@ export class LookupsView extends React.PureComponent { - await axios.delete( + await Api.instance.delete( `/druid/coordinator/v1/lookups/config/${deleteLookupTier}/${deleteLookupName}`, ); }} diff --git a/web-console/src/views/query-view/__snapshots__/query-view.spec.tsx.snap b/web-console/src/views/query-view/__snapshots__/query-view.spec.tsx.snap index dc188d44861..a4823ee6582 100644 --- a/web-console/src/views/query-view/__snapshots__/query-view.spec.tsx.snap +++ b/web-console/src/views/query-view/__snapshots__/query-view.spec.tsx.snap @@ -32,7 +32,7 @@ exports[`sql view matches snapshot 1`] = ` runeMode={false} />
{parsedQuery && - oneOf(columnData.DATA_TYPE, 'BIGINT', 'FLOAT') && ( + oneOf(columnData.DATA_TYPE, 'BIGINT', 'FLOAT', 'DOUBLE') && ( { - return axios.post(`/druid/v2${isSql ? '/sql' : ''}`, payload, { cancelToken }); + return Api.instance.post(`/druid/v2${isSql ? '/sql' : ''}`, payload, { cancelToken }); }); this.queryManager = new QueryManager({ @@ -482,7 +481,7 @@ export class QueryView extends React.PureComponent -
+
this.setState({ editContextDialogOpen: true })} runeMode={runeMode} 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 5dccedf388a..8d9d68af062 100644 --- a/web-console/src/views/segments-view/segments-view.spec.tsx +++ b/web-console/src/views/segments-view/segments-view.spec.tsx @@ -19,7 +19,7 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { Capabilities } from '../../utils/capabilities'; +import { Capabilities } from '../../utils'; import { SegmentsView } from '../segments-view/segments-view'; describe('segments-view', () => { diff --git a/web-console/src/views/segments-view/segments-view.tsx b/web-console/src/views/segments-view/segments-view.tsx index 4d2f64913ec..08568b8a53e 100644 --- a/web-console/src/views/segments-view/segments-view.tsx +++ b/web-console/src/views/segments-view/segments-view.tsx @@ -18,7 +18,6 @@ import { Button, ButtonGroup, Intent, Label, MenuItem } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import axios from 'axios'; import { SqlExpression, SqlRef } from 'druid-query-toolkit'; import React from 'react'; import ReactTable, { Filter } from 'react-table'; @@ -36,6 +35,7 @@ import { } from '../../components'; import { AsyncActionDialog } from '../../dialogs'; import { SegmentTableActionDialog } from '../../dialogs/segments-table-action-dialog/segment-table-action-dialog'; +import { Api } from '../../singletons'; import { addFilter, compact, @@ -50,8 +50,8 @@ import { QueryState, sqlQueryCustomTableFilter, } from '../../utils'; +import { Capabilities, CapabilitiesMode } from '../../utils'; import { BasicAction } from '../../utils/basic-action'; -import { Capabilities, CapabilitiesMode } from '../../utils/capabilities'; import { LocalStorageBackedArray } from '../../utils/local-storage-backed-array'; import './segments-view.scss'; @@ -295,11 +295,13 @@ export class SegmentsView extends React.PureComponent { - const datasourceList = (await axios.get('/druid/coordinator/v1/metadata/datasources')).data; + const datasourceList = (await Api.instance.get( + '/druid/coordinator/v1/metadata/datasources', + )).data; const nestedResults: SegmentQueryResultRow[][] = await Promise.all( datasourceList.map(async (d: string) => { - const segments = (await axios.get(`/druid/coordinator/v1/datasources/${d}?full`)).data - .segments; + const segments = (await Api.instance.get(`/druid/coordinator/v1/datasources/${d}?full`)) + .data.segments; return segments.map( (segment: any): SegmentQueryResultRow => { @@ -634,7 +636,7 @@ export class SegmentsView extends React.PureComponent { - const resp = await axios.delete( + const resp = await Api.instance.delete( `/druid/coordinator/v1/datasources/${terminateDatasourceId}/segments/${terminateSegmentId}`, {}, ); diff --git a/web-console/src/views/services-view/services-view.spec.tsx b/web-console/src/views/services-view/services-view.spec.tsx index 6b083ce4f16..32a5a6ef412 100644 --- a/web-console/src/views/services-view/services-view.spec.tsx +++ b/web-console/src/views/services-view/services-view.spec.tsx @@ -19,7 +19,7 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { Capabilities } from '../../utils/capabilities'; +import { Capabilities } from '../../utils'; import { ServicesView } from './services-view'; diff --git a/web-console/src/views/services-view/services-view.tsx b/web-console/src/views/services-view/services-view.tsx index 1f0280dd443..38d58d6253b 100644 --- a/web-console/src/views/services-view/services-view.tsx +++ b/web-console/src/views/services-view/services-view.tsx @@ -18,7 +18,6 @@ import { Button, ButtonGroup, Intent, Label, MenuItem } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import axios from 'axios'; import { sum } from 'd3-array'; import React from 'react'; import ReactTable from 'react-table'; @@ -35,6 +34,7 @@ import { ViewControlBar, } from '../../components'; import { AsyncActionDialog } from '../../dialogs'; +import { Api } from '../../singletons'; import { addFilter, Capabilities, @@ -182,7 +182,7 @@ FROM sys.servers ORDER BY "rank" DESC, "service" DESC`; static async getServices(): Promise { - const allServiceResp = await axios.get('/druid/coordinator/v1/servers?simple'); + const allServiceResp = await Api.instance.get('/druid/coordinator/v1/servers?simple'); const allServices = allServiceResp.data; return allServices.map((s: any) => { return { @@ -221,7 +221,9 @@ ORDER BY "rank" DESC, "service" DESC`; } if (capabilities.hasCoordinatorAccess()) { - const loadQueueResponse = await axios.get('/druid/coordinator/v1/loadqueue?simple'); + const loadQueueResponse = await Api.instance.get( + '/druid/coordinator/v1/loadqueue?simple', + ); const loadQueues: Record = loadQueueResponse.data; services = services.map(s => { const loadQueueInfo = loadQueues[s.service]; @@ -235,7 +237,7 @@ ORDER BY "rank" DESC, "service" DESC`; if (capabilities.hasOverlordAccess()) { let middleManagers: MiddleManagerQueryResultRow[]; try { - const middleManagerResponse = await axios.get('/druid/indexer/v1/workers'); + const middleManagerResponse = await Api.instance.get('/druid/indexer/v1/workers'); middleManagers = middleManagerResponse.data; } catch (e) { if ( @@ -589,7 +591,7 @@ ORDER BY "rank" DESC, "service" DESC`; return ( { - const resp = await axios.post( + const resp = await Api.instance.post( `/druid/indexer/v1/worker/${middleManagerDisableWorkerHost}/disable`, {}, ); @@ -618,7 +620,7 @@ ORDER BY "rank" DESC, "service" DESC`; return ( { - const resp = await axios.post( + const resp = await Api.instance.post( `/druid/indexer/v1/worker/${middleManagerEnableWorkerHost}/enable`, {}, );