This is an automated email from the ASF dual-hosted git repository.
vogievetsky pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new 877784e5fd1 Web console: add expectedLoadTimeMillis (#17359)
877784e5fd1 is described below
commit 877784e5fd1b26fb54b8d7eaefadbc2434dd3b3d
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Wed Oct 16 13:14:27 2024 -0700
Web console: add expectedLoadTimeMillis (#17359)
* add expectedLoadTimeMillis
* make spec cleaning less agro
* more cleanup
---
.../segment-timeline/segment-timeline.tsx | 16 +-
web-console/src/components/show-json/show-json.tsx | 4 +-
web-console/src/components/show-log/show-log.tsx | 3 +-
.../supervisor-history-panel.tsx | 3 +-
.../compaction-dynamic-config-dialog.tsx | 6 +-
.../compaction-history-dialog.tsx | 3 +-
.../coordinator-dynamic-config-dialog.tsx | 10 +-
.../datasource-columns-table.tsx | 11 +-
.../lookup-values-table/lookup-values-table.tsx | 11 +-
.../overlord-dynamic-config-dialog.tsx | 10 +-
.../dialogs/retention-dialog/retention-dialog.tsx | 18 +-
.../src/dialogs/status-dialog/status-dialog.tsx | 4 +-
.../supervisor-reset-offsets-dialog.tsx | 3 +-
.../supervisor-statistics-table.tsx | 4 +-
.../druid-models/ingestion-spec/ingestion-spec.tsx | 37 ++-
web-console/src/utils/druid-query.ts | 7 +-
.../views/datasources-view/datasources-view.tsx | 9 +-
.../datasources-card/datasources-card.tsx | 17 +-
.../views/home-view/lookups-card/lookups-card.tsx | 8 +-
.../home-view/segments-card/segments-card.tsx | 16 +-
.../home-view/services-card/services-card.tsx | 17 +-
.../views/home-view/status-card/status-card.tsx | 8 +-
.../supervisors-card/supervisors-card.tsx | 13 +-
.../src/views/home-view/tasks-card/tasks-card.tsx | 23 +-
.../src/views/lookups-view/lookups-view.tsx | 7 +-
.../src/views/segments-view/segments-view.tsx | 7 +-
.../src/views/services-view/services-view.tsx | 271 +++++++++++----------
.../schema-step/schema-step.tsx | 16 +-
web-console/src/views/tasks-view/tasks-view.tsx | 13 +-
.../current-dart-panel/current-dart-panel.tsx | 4 +-
.../execution-details-pane-loader.tsx | 6 +-
.../execution-stages-pane-loader.tsx | 6 +-
.../explain-dialog/explain-dialog.tsx | 10 +-
.../input-format-step/input-format-step.tsx | 4 +-
.../recent-query-task-panel.tsx | 11 +-
.../src/views/workbench-view/workbench-view.tsx | 11 +-
36 files changed, 373 insertions(+), 254 deletions(-)
diff --git a/web-console/src/components/segment-timeline/segment-timeline.tsx
b/web-console/src/components/segment-timeline/segment-timeline.tsx
index 0ea19555bfc..8aee0c66d47 100644
--- a/web-console/src/components/segment-timeline/segment-timeline.tsx
+++ b/web-console/src/components/segment-timeline/segment-timeline.tsx
@@ -269,18 +269,23 @@ ORDER BY "start" DESC`;
};
this.dataQueryManager = new QueryManager({
- processQuery: async ({ capabilities, dateRange }) => {
+ processQuery: async ({ capabilities, dateRange }, cancelToken) => {
let intervals: IntervalRow[];
let datasources: string[];
if (capabilities.hasSql()) {
- intervals = await queryDruidSql({
- query: SegmentTimeline.getSqlQuery(dateRange),
- });
+ intervals = await queryDruidSql(
+ {
+ query: SegmentTimeline.getSqlQuery(dateRange),
+ },
+ cancelToken,
+ );
datasources = uniq(intervals.map(r => r.datasource).sort());
} else if (capabilities.hasCoordinatorAccess()) {
const startIso = dateRange[0].toISOString();
- datasources = (await
Api.instance.get(`/druid/coordinator/v1/datasources`)).data;
+ datasources = (
+ await Api.instance.get(`/druid/coordinator/v1/datasources`, {
cancelToken })
+ ).data;
intervals = (
await Promise.all(
datasources.map(async datasource => {
@@ -289,6 +294,7 @@ ORDER BY "start" DESC`;
`/druid/coordinator/v1/datasources/${Api.encodePath(
datasource,
)}/intervals?simple`,
+ { cancelToken },
)
).data;
diff --git a/web-console/src/components/show-json/show-json.tsx
b/web-console/src/components/show-json/show-json.tsx
index 8f48a2a98b9..e122be709c8 100644
--- a/web-console/src/components/show-json/show-json.tsx
+++ b/web-console/src/components/show-json/show-json.tsx
@@ -39,8 +39,8 @@ export const ShowJson = React.memo(function ShowJson(props:
ShowJsonProps) {
const { endpoint, transform, downloadFilename } = props;
const [jsonState] = useQueryManager<null, string>({
- processQuery: async () => {
- const resp = await Api.instance.get(endpoint);
+ processQuery: async (_, cancelToken) => {
+ const resp = await Api.instance.get(endpoint, { cancelToken });
let data = resp.data;
if (transform) data = transform(data);
return typeof data === 'string' ? data : JSONBig.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 7f4a0eaa381..3f597351e7e 100644
--- a/web-console/src/components/show-log/show-log.tsx
+++ b/web-console/src/components/show-log/show-log.tsx
@@ -62,10 +62,11 @@ export class ShowLog extends
React.PureComponent<ShowLogProps, ShowLogState> {
};
this.showLogQueryManager = new QueryManager({
- processQuery: async () => {
+ processQuery: async (_, cancelToken) => {
const { endpoint, tailOffset } = this.props;
const resp = await Api.instance.get(
endpoint + (tailOffset ? `?offset=-${tailOffset}` : ''),
+ { cancelToken },
);
const data = resp.data;
diff --git
a/web-console/src/components/supervisor-history-panel/supervisor-history-panel.tsx
b/web-console/src/components/supervisor-history-panel/supervisor-history-panel.tsx
index 8af5c2df03f..74cb55682f3 100644
---
a/web-console/src/components/supervisor-history-panel/supervisor-history-panel.tsx
+++
b/web-console/src/components/supervisor-history-panel/supervisor-history-panel.tsx
@@ -48,9 +48,10 @@ export const SupervisorHistoryPanel = React.memo(function
SupervisorHistoryPanel
const [diffIndex, setDiffIndex] = useState(-1);
const [historyState] = useQueryManager<string, SupervisorHistoryEntry[]>({
initQuery: supervisorId,
- processQuery: async supervisorId => {
+ processQuery: async (supervisorId, cancelToken) => {
const resp = await Api.instance.get(
`/druid/indexer/v1/supervisor/${Api.encodePath(supervisorId)}/history`,
+ { cancelToken },
);
return resp.data.map((vs: SupervisorHistoryEntry) => deepSet(vs, 'spec',
cleanSpec(vs.spec)));
},
diff --git
a/web-console/src/dialogs/compaction-dynamic-config-dialog/compaction-dynamic-config-dialog.tsx
b/web-console/src/dialogs/compaction-dynamic-config-dialog/compaction-dynamic-config-dialog.tsx
index be5234b5bdb..1b6bb587aab 100644
---
a/web-console/src/dialogs/compaction-dynamic-config-dialog/compaction-dynamic-config-dialog.tsx
+++
b/web-console/src/dialogs/compaction-dynamic-config-dialog/compaction-dynamic-config-dialog.tsx
@@ -64,9 +64,11 @@ export const CompactionDynamicConfigDialog =
React.memo(function CompactionDynam
useQueryManager<null, Record<string, any>>({
initQuery: null,
- processQuery: async () => {
+ processQuery: async (_, cancelToken) => {
try {
- const c = (await
Api.instance.get('/druid/coordinator/v1/config/compaction')).data;
+ const c = (
+ await Api.instance.get('/druid/coordinator/v1/config/compaction', {
cancelToken })
+ ).data;
setDynamicConfig({
compactionTaskSlotRatio: c.compactionTaskSlotRatio ?? DEFAULT_RATIO,
maxCompactionTaskSlots: c.maxCompactionTaskSlots ?? DEFAULT_MAX,
diff --git
a/web-console/src/dialogs/compaction-history-dialog/compaction-history-dialog.tsx
b/web-console/src/dialogs/compaction-history-dialog/compaction-history-dialog.tsx
index c654a1f9b77..9e19e043c71 100644
---
a/web-console/src/dialogs/compaction-history-dialog/compaction-history-dialog.tsx
+++
b/web-console/src/dialogs/compaction-history-dialog/compaction-history-dialog.tsx
@@ -63,10 +63,11 @@ export const CompactionHistoryDialog = React.memo(function
CompactionHistoryDial
const [diffIndex, setDiffIndex] = useState(-1);
const [historyState] = useQueryManager<string, CompactionHistoryEntry[]>({
initQuery: datasource,
- processQuery: async datasource => {
+ processQuery: async (datasource, cancelToken) => {
try {
const resp = await Api.instance.get(
`/druid/coordinator/v1/config/compaction/${Api.encodePath(datasource)}/history?count=20`,
+ { cancelToken },
);
return resp.data;
} catch (e) {
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 3caa2c71b74..ec964f5507e 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
@@ -46,17 +46,19 @@ export const CoordinatorDynamicConfigDialog =
React.memo(function CoordinatorDyn
const [historyRecordsState] = useQueryManager<null, any[]>({
initQuery: null,
- processQuery: async () => {
- const historyResp = await
Api.instance.get(`/druid/coordinator/v1/config/history?count=100`);
+ processQuery: async (_, cancelToken) => {
+ const historyResp = await
Api.instance.get(`/druid/coordinator/v1/config/history?count=100`, {
+ cancelToken,
+ });
return historyResp.data;
},
});
useQueryManager<null, Record<string, any>>({
initQuery: null,
- processQuery: async () => {
+ processQuery: async (_, cancelToken) => {
try {
- const configResp = await
Api.instance.get('/druid/coordinator/v1/config');
+ const configResp = await
Api.instance.get('/druid/coordinator/v1/config', { cancelToken });
setDynamicConfig(configResp.data || {});
} catch (e) {
AppToaster.show({
diff --git
a/web-console/src/dialogs/datasource-table-action-dialog/datasource-columns-table/datasource-columns-table.tsx
b/web-console/src/dialogs/datasource-table-action-dialog/datasource-columns-table/datasource-columns-table.tsx
index 040a978c409..82187c866f4 100644
---
a/web-console/src/dialogs/datasource-table-action-dialog/datasource-columns-table/datasource-columns-table.tsx
+++
b/web-console/src/dialogs/datasource-table-action-dialog/datasource-columns-table/datasource-columns-table.tsx
@@ -42,11 +42,14 @@ export const DatasourceColumnsTable = React.memo(function
DatasourceColumnsTable
) {
const [columnsState] = useQueryManager<string, DatasourceColumnsTableRow[]>({
initQuery: props.datasource,
- processQuery: async (datasourceId: string) => {
- return await queryDruidSql<ColumnMetadata>({
- query: `SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS
+ processQuery: async (datasourceId, cancelToken) => {
+ return await queryDruidSql<ColumnMetadata>(
+ {
+ query: `SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'druid' AND TABLE_NAME = ${L(datasourceId)}`,
- });
+ },
+ cancelToken,
+ );
},
});
diff --git
a/web-console/src/dialogs/lookup-table-action-dialog/lookup-values-table/lookup-values-table.tsx
b/web-console/src/dialogs/lookup-table-action-dialog/lookup-values-table/lookup-values-table.tsx
index 5898486e630..c36445d1773 100644
---
a/web-console/src/dialogs/lookup-table-action-dialog/lookup-values-table/lookup-values-table.tsx
+++
b/web-console/src/dialogs/lookup-table-action-dialog/lookup-values-table/lookup-values-table.tsx
@@ -41,10 +41,13 @@ export const LookupValuesTable = React.memo(function
LookupValuesTable(
props: LookupValuesTableProps,
) {
const [entriesState] = useQueryManager<string, LookupRow[]>({
- processQuery: async (lookupId: string) => {
- return await queryDruidSql<LookupRow>({
- query: `SELECT "k", "v" FROM ${N('lookup').table(lookupId)} LIMIT
5000`,
- });
+ processQuery: async (lookupId, cancelToken) => {
+ return await queryDruidSql<LookupRow>(
+ {
+ query: `SELECT "k", "v" FROM ${N('lookup').table(lookupId)} LIMIT
5000`,
+ },
+ cancelToken,
+ );
},
initQuery: props.lookupId,
});
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 64e5a6d168a..ba30118b0a3 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
@@ -46,17 +46,19 @@ export const OverlordDynamicConfigDialog =
React.memo(function OverlordDynamicCo
const [historyRecordsState] = useQueryManager<null, any[]>({
initQuery: null,
- processQuery: async () => {
- const historyResp = await
Api.instance.get(`/druid/indexer/v1/worker/history?count=100`);
+ processQuery: async (_, cancelToken) => {
+ const historyResp = await
Api.instance.get(`/druid/indexer/v1/worker/history?count=100`, {
+ cancelToken,
+ });
return historyResp.data;
},
});
useQueryManager<null, Record<string, any>>({
initQuery: null,
- processQuery: async () => {
+ processQuery: async (_, cancelToken) => {
try {
- const configResp = await Api.instance.get(`/druid/indexer/v1/worker`);
+ const configResp = await Api.instance.get(`/druid/indexer/v1/worker`,
{ cancelToken });
setDynamicConfig(configResp.data || {});
} catch (e) {
AppToaster.show({
diff --git a/web-console/src/dialogs/retention-dialog/retention-dialog.tsx
b/web-console/src/dialogs/retention-dialog/retention-dialog.tsx
index ac231e8e991..9b657622d89 100644
--- a/web-console/src/dialogs/retention-dialog/retention-dialog.tsx
+++ b/web-console/src/dialogs/retention-dialog/retention-dialog.tsx
@@ -52,19 +52,24 @@ export const RetentionDialog = React.memo(function
RetentionDialog(props: Retent
const [tiersState] = useQueryManager<Capabilities, string[]>({
initQuery: capabilities,
- processQuery: async capabilities => {
+ processQuery: async (capabilities, cancelToken) => {
if (capabilities.hasSql()) {
- const sqlResp = await queryDruidSql<{ tier: string }>({
- query: `SELECT "tier"
+ const sqlResp = await queryDruidSql<{ tier: string }>(
+ {
+ query: `SELECT "tier"
FROM "sys"."servers"
WHERE "server_type" = 'historical'
GROUP BY 1
ORDER BY 1`,
- });
+ },
+ cancelToken,
+ );
return sqlResp.map(d => d.tier);
} else if (capabilities.hasCoordinatorAccess()) {
- const allServiceResp = await
Api.instance.get('/druid/coordinator/v1/servers?simple');
+ const allServiceResp = await
Api.instance.get('/druid/coordinator/v1/servers?simple', {
+ cancelToken,
+ });
return filterMap(allServiceResp.data, (s: any) =>
s.type === 'historical' ? s.tier : undefined,
);
@@ -78,9 +83,10 @@ ORDER BY 1`,
const [historyQueryState] = useQueryManager<string, any[]>({
initQuery: props.datasource,
- processQuery: async datasource => {
+ processQuery: async (datasource, cancelToken) => {
const historyResp = await Api.instance.get(
`/druid/coordinator/v1/rules/${Api.encodePath(datasource)}/history?count=200`,
+ { cancelToken },
);
return historyResp.data;
},
diff --git a/web-console/src/dialogs/status-dialog/status-dialog.tsx
b/web-console/src/dialogs/status-dialog/status-dialog.tsx
index 559a9111300..311f3e05664 100644
--- a/web-console/src/dialogs/status-dialog/status-dialog.tsx
+++ b/web-console/src/dialogs/status-dialog/status-dialog.tsx
@@ -49,8 +49,8 @@ export const StatusDialog = React.memo(function
StatusDialog(props: StatusDialog
const [responseState] = useQueryManager<null, StatusResponse>({
initQuery: null,
- processQuery: async () => {
- const resp = await Api.instance.get(`/status`);
+ processQuery: async (_, cancelToken) => {
+ const resp = await Api.instance.get(`/status`, { cancelToken });
return resp.data;
},
});
diff --git
a/web-console/src/dialogs/supervisor-reset-offsets-dialog/supervisor-reset-offsets-dialog.tsx
b/web-console/src/dialogs/supervisor-reset-offsets-dialog/supervisor-reset-offsets-dialog.tsx
index b0661f0c7d8..da0b4fc31d1 100644
---
a/web-console/src/dialogs/supervisor-reset-offsets-dialog/supervisor-reset-offsets-dialog.tsx
+++
b/web-console/src/dialogs/supervisor-reset-offsets-dialog/supervisor-reset-offsets-dialog.tsx
@@ -104,9 +104,10 @@ export const SupervisorResetOffsetsDialog =
React.memo(function SupervisorResetO
const [statusResp] = useQueryManager<string, SupervisorStatus>({
initQuery: supervisorId,
- processQuery: async supervisorId => {
+ processQuery: async (supervisorId, cancelToken) => {
const statusResp = await Api.instance.get(
`/druid/indexer/v1/supervisor/${Api.encodePath(supervisorId)}/status`,
+ { cancelToken },
);
return statusResp.data;
},
diff --git
a/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-statistics-table/supervisor-statistics-table.tsx
b/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-statistics-table/supervisor-statistics-table.tsx
index 49525dfb870..d0c9ee8756c 100644
---
a/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-statistics-table/supervisor-statistics-table.tsx
+++
b/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-statistics-table/supervisor-statistics-table.tsx
@@ -60,8 +60,8 @@ export const SupervisorStatisticsTable = React.memo(function
SupervisorStatistic
SupervisorStatisticsTableRow[]
>({
initQuery: null,
- processQuery: async () => {
- const resp = await Api.instance.get<SupervisorStats>(statsEndpoint);
+ processQuery: async (_, cancelToken) => {
+ const resp = await Api.instance.get<SupervisorStats>(statsEndpoint, {
cancelToken });
return normalizeSupervisorStatisticsResults(resp.data);
},
});
diff --git a/web-console/src/druid-models/ingestion-spec/ingestion-spec.tsx
b/web-console/src/druid-models/ingestion-spec/ingestion-spec.tsx
index 2071aa2c377..09ad1d73921 100644
--- a/web-console/src/druid-models/ingestion-spec/ingestion-spec.tsx
+++ b/web-console/src/druid-models/ingestion-spec/ingestion-spec.tsx
@@ -27,12 +27,12 @@ import { AutoForm, ExternalLink } from '../../components';
import { IndexSpecDialog } from
'../../dialogs/index-spec-dialog/index-spec-dialog';
import { getLink } from '../../links';
import {
- allowKeys,
deepDelete,
deepGet,
deepMove,
deepSet,
deepSetIfUnset,
+ deleteKeys,
EMPTY_ARRAY,
EMPTY_OBJECT,
filterMap,
@@ -79,6 +79,11 @@ export interface IngestionSpec {
readonly spec: IngestionSpecInner;
readonly context?: { useConcurrentLocks?: boolean };
readonly suspended?: boolean;
+
+ // Added by the server
+ readonly id?: string;
+ readonly groupId?: string;
+ readonly resource?: any;
}
export interface IngestionSpecInner {
@@ -490,11 +495,37 @@ export function normalizeSpec(spec:
Partial<IngestionSpec>): IngestionSpec {
}
/**
- * Make sure that any extra junk in the spec other than 'type', 'spec', and
'context' is removed
+ * This function cleans a spec that was returned by the server so that it can
be re-opened in the data loader to be
+ * submitted again.
* @param spec - the spec to clean
*/
export function cleanSpec(spec: Partial<IngestionSpec>):
Partial<IngestionSpec> {
- return allowKeys(spec, ['type', 'spec', 'context', 'suspended']) as
IngestionSpec;
+ const specSpec = spec.spec;
+
+ // For backwards compatible reasons the contents of `spec` (`dataSchema`,
`ioConfig`, and `tuningConfig`)
+ // can be duplicated at the top level. This function removes these
duplicates (if needed) so that there is no confusion
+ // which is the authoritative copy.
+ if (
+ specSpec &&
+ specSpec.dataSchema &&
+ specSpec.ioConfig &&
+ specSpec.tuningConfig &&
+ (spec as any).dataSchema &&
+ (spec as any).ioConfig &&
+ (spec as any).tuningConfig
+ ) {
+ spec = deleteKeys(spec, ['dataSchema', 'ioConfig', 'tuningConfig'] as
any[]);
+ }
+
+ // Sometimes the dataSource can (redundantly) make it to the top level for
some reason - delete it
+ if (
+ typeof specSpec?.dataSchema?.dataSource === 'string' &&
+ typeof (spec as any).dataSource === 'string'
+ ) {
+ spec = deleteKeys(spec, ['dataSource'] as any[]);
+ }
+
+ return deleteKeys(spec, ['id', 'groupId', 'resource']);
}
export function upgradeSpec(spec: any, yolo = false): Partial<IngestionSpec> {
diff --git a/web-console/src/utils/druid-query.ts
b/web-console/src/utils/druid-query.ts
index d1481366fa7..8102db89ca3 100644
--- a/web-console/src/utils/druid-query.ts
+++ b/web-console/src/utils/druid-query.ts
@@ -319,10 +319,13 @@ export class DruidError extends Error {
}
}
-export async function queryDruidRune(runeQuery: Record<string, any>):
Promise<any> {
+export async function queryDruidRune(
+ runeQuery: Record<string, any>,
+ cancelToken?: CancelToken,
+): Promise<any> {
let runeResultResp: AxiosResponse;
try {
- runeResultResp = await Api.instance.post('/druid/v2', runeQuery);
+ runeResultResp = await Api.instance.post('/druid/v2', runeQuery, {
cancelToken });
} catch (e) {
throw new Error(getDruidErrorMessage(e));
}
diff --git a/web-console/src/views/datasources-view/datasources-view.tsx
b/web-console/src/views/datasources-view/datasources-view.tsx
index 96a32c545ab..7a7ad2c33f1 100644
--- a/web-console/src/views/datasources-view/datasources-view.tsx
+++ b/web-console/src/views/datasources-view/datasources-view.tsx
@@ -426,19 +426,22 @@ GROUP BY 1, 2`;
this.datasourceQueryManager = new QueryManager<DatasourceQuery,
DatasourcesAndDefaultRules>({
processQuery: async (
{ capabilities, visibleColumns, showUnused },
- _cancelToken,
+ cancelToken,
setIntermediateQuery,
) => {
let datasources: DatasourceQueryResultRow[];
if (capabilities.hasSql()) {
const query = DatasourcesView.query(visibleColumns);
setIntermediateQuery(query);
- datasources = await queryDruidSql({ query });
+ datasources = await queryDruidSql({ query }, cancelToken);
} else if (capabilities.hasCoordinatorAccess()) {
const datasourcesResp = await Api.instance.get(
'/druid/coordinator/v1/datasources?simple',
+ { cancelToken },
);
- const loadstatusResp = await
Api.instance.get('/druid/coordinator/v1/loadstatus?simple');
+ const loadstatusResp = await
Api.instance.get('/druid/coordinator/v1/loadstatus?simple', {
+ cancelToken,
+ });
const loadstatus = loadstatusResp.data;
datasources = datasourcesResp.data.map((d: any):
DatasourceQueryResultRow => {
const totalDataSize = deepGet(d, 'properties.segments.size') || -1;
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 3f64e0476e1..811187117e7 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
@@ -31,14 +31,20 @@ export interface DatasourcesCardProps {
export const DatasourcesCard = React.memo(function DatasourcesCard(props:
DatasourcesCardProps) {
const [datasourceCountState] = useQueryManager<Capabilities, number>({
- processQuery: async capabilities => {
+ initQuery: props.capabilities,
+ processQuery: async (capabilities, cancelToken) => {
let datasources: any[];
if (capabilities.hasSql()) {
- datasources = await queryDruidSql({
- query: `SELECT datasource FROM sys.segments GROUP BY 1`,
- });
+ datasources = await queryDruidSql(
+ {
+ query: `SELECT datasource FROM sys.segments GROUP BY 1`,
+ },
+ cancelToken,
+ );
} else if (capabilities.hasCoordinatorAccess()) {
- const datasourcesResp = await
Api.instance.get('/druid/coordinator/v1/datasources');
+ const datasourcesResp = await
Api.instance.get('/druid/coordinator/v1/datasources', {
+ cancelToken,
+ });
datasources = datasourcesResp.data;
} else {
throw new Error(`must have SQL or coordinator access`);
@@ -46,7 +52,6 @@ export const DatasourcesCard = React.memo(function
DatasourcesCard(props: Dataso
return datasources.length;
},
- initQuery: props.capabilities,
});
return (
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 e99ef2ab67a..ee73c4cd0cf 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
@@ -32,16 +32,18 @@ export interface LookupsCardProps {
export const LookupsCard = React.memo(function LookupsCard(props:
LookupsCardProps) {
const [lookupsCountState] = useQueryManager<Capabilities, number>({
- processQuery: async capabilities => {
+ initQuery: props.capabilities,
+ processQuery: async (capabilities, cancelToken) => {
if (capabilities.hasCoordinatorAccess()) {
- const resp = await
Api.instance.get('/druid/coordinator/v1/lookups/status');
+ const resp = await
Api.instance.get('/druid/coordinator/v1/lookups/status', {
+ cancelToken,
+ });
const data = resp.data;
return sum(Object.keys(data).map(k => Object.keys(data[k]).length));
} else {
throw new Error(`must have coordinator access`);
}
},
- initQuery: props.capabilities,
});
return (
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 8db32294882..d5d157a523b 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
@@ -40,25 +40,31 @@ export interface SegmentsCardProps {
export const SegmentsCard = React.memo(function SegmentsCard(props:
SegmentsCardProps) {
const [segmentCountState] = useQueryManager<Capabilities, SegmentCounts>({
initQuery: props.capabilities,
- processQuery: async capabilities => {
+ processQuery: async (capabilities, cancelToken) => {
if (capabilities.hasSql()) {
- const segments = await queryDruidSql({
- query: `SELECT
+ const segments = await queryDruidSql(
+ {
+ query: `SELECT
COUNT(*) AS "active",
COUNT(*) FILTER (WHERE is_available = 1) AS "cached_on_historical",
COUNT(*) FILTER (WHERE is_available = 0 AND replication_factor > 0) AS
"unavailable",
COUNT(*) FILTER (WHERE is_realtime = 1) AS "realtime"
FROM sys.segments
WHERE is_active = 1`,
- });
+ },
+ cancelToken,
+ );
return segments.length === 1 ? segments[0] : null;
} else if (capabilities.hasCoordinatorAccess()) {
- const loadstatusResp = await
Api.instance.get('/druid/coordinator/v1/loadstatus?simple');
+ const loadstatusResp = await
Api.instance.get('/druid/coordinator/v1/loadstatus?simple', {
+ cancelToken,
+ });
const loadstatus = loadstatusResp.data;
const unavailableSegmentNum = sum(Object.keys(loadstatus), key =>
loadstatus[key]);
const datasourcesMetaResp = await Api.instance.get(
'/druid/coordinator/v1/datasources?simple',
+ { cancelToken },
);
const datasourcesMeta = datasourcesMetaResp.data;
const availableSegmentNum = sum(datasourcesMeta, (curr: any) =>
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 6018b5e91a5..18a66b83218 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
@@ -43,24 +43,29 @@ export interface ServicesCardProps {
export const ServicesCard = React.memo(function ServicesCard(props:
ServicesCardProps) {
const [serviceCountState] = useQueryManager<Capabilities, ServiceCounts>({
- processQuery: async capabilities => {
+ processQuery: async (capabilities, cancelToken) => {
if (capabilities.hasSql()) {
const serviceCountsFromQuery: {
service_type: string;
count: number;
- }[] = await queryDruidSql({
- query: `SELECT server_type AS "service_type", COUNT(*) as "count"
FROM sys.servers GROUP BY 1`,
- });
+ }[] = await queryDruidSql(
+ {
+ query: `SELECT server_type AS "service_type", COUNT(*) as "count"
FROM sys.servers GROUP BY 1`,
+ },
+ cancelToken,
+ );
return lookupBy(
serviceCountsFromQuery,
x => x.service_type,
x => x.count,
);
} else if (capabilities.hasCoordinatorAccess()) {
- const services = (await
Api.instance.get('/druid/coordinator/v1/servers?simple')).data;
+ const services = (
+ await Api.instance.get('/druid/coordinator/v1/servers?simple', {
cancelToken })
+ ).data;
const middleManager = capabilities.hasOverlordAccess()
- ? (await Api.instance.get('/druid/indexer/v1/workers')).data
+ ? (await Api.instance.get('/druid/indexer/v1/workers', { cancelToken
})).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 f63cd0a8bc8..da85ab532cd 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
@@ -49,8 +49,8 @@ export const StatusCard = React.memo(function
StatusCard(props: StatusCardProps)
const [showStatusDialog, setShowStatusDialog] = useState(false);
const [statusSummaryState] = useQueryManager<null, StatusSummary>({
initQuery: null,
- processQuery: async () => {
- const statusResp = await Api.instance.get('/status');
+ processQuery: async (_, cancelToken) => {
+ const statusResp = await Api.instance.get('/status', { cancelToken });
return {
version: statusResp.data.version,
extensionCount: statusResp.data.modules.length,
@@ -60,9 +60,9 @@ export const StatusCard = React.memo(function
StatusCard(props: StatusCardProps)
const [nullModeDetectionState] = useQueryManager<Capabilities,
NullModeDetection>({
initQuery: capabilities,
- processQuery: async capabilities => {
+ processQuery: async (capabilities, cancelToken) => {
if (!capabilities.hasQuerying()) return {};
- const nullDetectionResponse = await queryDruidRune(NULL_DETECTION_QUERY);
+ const nullDetectionResponse = await queryDruidRune(NULL_DETECTION_QUERY,
cancelToken);
return nullDetectionQueryResultDecoder(deepGet(nullDetectionResponse,
'0.result'));
},
});
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 3a44c4f89db..ab63e0205ac 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
@@ -36,18 +36,21 @@ export interface SupervisorsCardProps {
export const SupervisorsCard = React.memo(function SupervisorsCard(props:
SupervisorsCardProps) {
const [supervisorCountState] = useQueryManager<Capabilities,
SupervisorCounts>({
- processQuery: async capabilities => {
+ processQuery: async (capabilities, cancelToken) => {
if (capabilities.hasSql()) {
return (
- await queryDruidSql({
- query: `SELECT
+ await queryDruidSql(
+ {
+ query: `SELECT
COUNT(*) FILTER (WHERE "suspended" = 0) AS "running",
COUNT(*) FILTER (WHERE "suspended" = 1) AS "suspended"
FROM sys.supervisors`,
- })
+ },
+ cancelToken,
+ )
)[0];
} else if (capabilities.hasOverlordAccess()) {
- const resp = await
Api.instance.get('/druid/indexer/v1/supervisor?full');
+ const resp = await
Api.instance.get('/druid/indexer/v1/supervisor?full', { cancelToken });
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.tsx
b/web-console/src/views/home-view/tasks-card/tasks-card.tsx
index 3ee06e56b95..fa91b8a141e 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,6 +17,7 @@
*/
import { IconNames } from '@blueprintjs/icons';
+import type { CancelToken } from 'axios';
import React from 'react';
import { PluralPairIfNeeded } from '../../../components';
@@ -40,22 +41,28 @@ export interface TaskCounts {
waiting?: number;
}
-async function getTaskCounts(capabilities: Capabilities): Promise<TaskCounts> {
+async function getTaskCounts(
+ capabilities: Capabilities,
+ cancelToken: CancelToken,
+): Promise<TaskCounts> {
if (capabilities.hasSql()) {
- const taskCountsFromQuery = await queryDruidSql<{ status: string; count:
number }>({
- query: `SELECT
+ const taskCountsFromQuery = await queryDruidSql<{ status: string; count:
number }>(
+ {
+ query: `SELECT
CASE WHEN "status" = 'RUNNING' THEN "runner_status" ELSE "status" END AS
"status",
COUNT(*) AS "count"
FROM sys.tasks
GROUP BY 1`,
- });
+ },
+ cancelToken,
+ );
return lookupBy(
taskCountsFromQuery,
x => x.status.toLowerCase(),
x => x.count,
);
} else if (capabilities.hasOverlordAccess()) {
- const tasks: any[] = (await
Api.instance.get('/druid/indexer/v1/tasks')).data;
+ const tasks: any[] = (await Api.instance.get('/druid/indexer/v1/tasks', {
cancelToken })).data;
return {
success: tasks.filter(d => getTaskStatus(d) === 'SUCCESS').length,
failed: tasks.filter(d => getTaskStatus(d) === 'FAILED').length,
@@ -76,14 +83,14 @@ export interface TasksCardProps {
export const TasksCard = React.memo(function TasksCard(props: TasksCardProps) {
const [cardState] = useQueryManager<Capabilities, TaskCountsAndCapacity>({
- processQuery: async capabilities => {
- const taskCounts = await getTaskCounts(capabilities);
+ initQuery: props.capabilities,
+ processQuery: async (capabilities, cancelToken) => {
+ const taskCounts = await getTaskCounts(capabilities, cancelToken);
if (!capabilities.hasOverlordAccess()) return taskCounts;
const capacity = await getClusterCapacity();
return { ...taskCounts, ...capacity };
},
- initQuery: props.capabilities,
});
const { success, failed, running, pending, waiting, totalTaskSlots } =
cardState.data || {};
diff --git a/web-console/src/views/lookups-view/lookups-view.tsx
b/web-console/src/views/lookups-view/lookups-view.tsx
index b3df25717fe..1fada802998 100644
--- a/web-console/src/views/lookups-view/lookups-view.tsx
+++ b/web-console/src/views/lookups-view/lookups-view.tsx
@@ -123,16 +123,19 @@ export class LookupsView extends
React.PureComponent<LookupsViewProps, LookupsVi
};
this.lookupsQueryManager = new QueryManager({
- processQuery: async () => {
+ processQuery: async (_, cancelToken) => {
const tiersResp = await Api.instance.get(
'/druid/coordinator/v1/lookups/config?discover=true',
+ { cancelToken },
);
const tiers =
tiersResp.data && tiersResp.data.length > 0
? tiersResp.data.sort(tierNameCompare)
: [DEFAULT_LOOKUP_TIER];
- const lookupResp = await
Api.instance.get('/druid/coordinator/v1/lookups/config/all');
+ const lookupResp = await
Api.instance.get('/druid/coordinator/v1/lookups/config/all', {
+ cancelToken,
+ });
const lookupData = lookupResp.data;
const lookupEntries: LookupEntry[] = [];
diff --git a/web-console/src/views/segments-view/segments-view.tsx
b/web-console/src/views/segments-view/segments-view.tsx
index def5b2baba9..63fd9157b9f 100644
--- a/web-console/src/views/segments-view/segments-view.tsx
+++ b/web-console/src/views/segments-view/segments-view.tsx
@@ -255,7 +255,7 @@ END AS "time_span"`,
this.segmentsQueryManager = new QueryManager({
debounceIdle: 500,
- processQuery: async (query: SegmentsQuery, _cancelToken,
setIntermediateQuery) => {
+ processQuery: async (query: SegmentsQuery, cancelToken,
setIntermediateQuery) => {
const { page, pageSize, filtered, sorted, visibleColumns,
capabilities, groupByInterval } =
query;
@@ -356,10 +356,10 @@ END AS "time_span"`,
}
const sqlQuery = queryParts.join('\n');
setIntermediateQuery(sqlQuery);
- return await queryDruidSql({ query: sqlQuery });
+ return await queryDruidSql({ query: sqlQuery }, cancelToken);
} else if (capabilities.hasCoordinatorAccess()) {
let datasourceList: string[] = (
- await
Api.instance.get('/druid/coordinator/v1/metadata/datasources')
+ await
Api.instance.get('/druid/coordinator/v1/metadata/datasources', { cancelToken })
).data;
const datasourceFilter = filtered.find(({ id }) => id ===
'datasource');
@@ -383,6 +383,7 @@ END AS "time_span"`,
const segments = (
await Api.instance.get(
`/druid/coordinator/v1/datasources/${Api.encodePath(datasourceList[i])}?full`,
+ { cancelToken },
)
).data?.segments;
if (!Array.isArray(segments)) continue;
diff --git a/web-console/src/views/services-view/services-view.tsx
b/web-console/src/views/services-view/services-view.tsx
index ebff85dc81f..af7587d35b9 100644
--- a/web-console/src/views/services-view/services-view.tsx
+++ b/web-console/src/views/services-view/services-view.tsx
@@ -18,7 +18,7 @@
import { Button, ButtonGroup, Intent, Label, MenuItem, Tag } from
'@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
-import { sum } from 'd3-array';
+import { max, sum } from 'd3-array';
import React from 'react';
import type { Filter } from 'react-table';
import ReactTable from 'react-table';
@@ -40,21 +40,25 @@ import type { QueryWithContext } from '../../druid-models';
import type { Capabilities, CapabilitiesMode } from '../../helpers';
import { STANDARD_TABLE_PAGE_SIZE, STANDARD_TABLE_PAGE_SIZE_OPTIONS } from
'../../react-table';
import { Api, AppToaster } from '../../singletons';
-import type { NumberLike } from '../../utils';
+import type { AuxiliaryQueryFn, NumberLike } from '../../utils';
import {
+ assemble,
deepGet,
filterMap,
formatBytes,
formatBytesCompact,
+ formatDurationWithMsIfNeeded,
hasPopoverOpen,
LocalStorageBackedVisibility,
LocalStorageKeys,
lookupBy,
oneOf,
pluralIfNeeded,
+ prettyFormatIsoDateWithMsIfNeeded,
queryDruidSql,
QueryManager,
QueryState,
+ ResultWithAuxiliaryWork,
} from '../../utils';
import type { BasicAction } from '../../utils/basic-action';
@@ -97,28 +101,9 @@ const TABLE_COLUMNS_BY_MODE: Record<CapabilitiesMode,
TableColumnSelectorColumn[
],
};
-function formatQueues(
- segmentsToLoad: NumberLike,
- segmentsToLoadSize: NumberLike,
- segmentsToDrop: NumberLike,
- segmentsToDropSize: NumberLike,
-): string {
- const queueParts: string[] = [];
- if (segmentsToLoad) {
- queueParts.push(
- `${pluralIfNeeded(segmentsToLoad, 'segment')} to load
(${formatBytesCompact(
- segmentsToLoadSize,
- )})`,
- );
- }
- if (segmentsToDrop) {
- queueParts.push(
- `${pluralIfNeeded(segmentsToDrop, 'segment')} to drop
(${formatBytesCompact(
- segmentsToDropSize,
- )})`,
- );
- }
- return queueParts.join(', ') || 'Empty load/drop queues';
+interface ServicesQuery {
+ capabilities: Capabilities;
+ visibleColumns: LocalStorageBackedVisibility;
}
export interface ServicesViewProps {
@@ -149,8 +134,8 @@ interface ServiceResultRow {
readonly plaintext_port: number;
readonly tls_port: number;
readonly start_time: string;
- loadQueueInfo?: LoadQueueInfo;
- workerInfo?: WorkerInfo;
+ readonly loadQueueInfo?: LoadQueueInfo;
+ readonly workerInfo?: WorkerInfo;
}
interface LoadQueueInfo {
@@ -158,6 +143,44 @@ interface LoadQueueInfo {
readonly segmentsToDropSize: NumberLike;
readonly segmentsToLoad: NumberLike;
readonly segmentsToLoadSize: NumberLike;
+ readonly expectedLoadTimeMillis: NumberLike;
+}
+
+function formatLoadQueueInfo({
+ segmentsToDrop,
+ segmentsToDropSize,
+ segmentsToLoad,
+ segmentsToLoadSize,
+ expectedLoadTimeMillis,
+}: LoadQueueInfo): string {
+ return (
+ assemble(
+ segmentsToLoad
+ ? `${pluralIfNeeded(segmentsToLoad, 'segment')} to load
(${formatBytesCompact(
+ segmentsToLoadSize,
+ )}${
+ expectedLoadTimeMillis
+ ? `, ${formatDurationWithMsIfNeeded(expectedLoadTimeMillis)}`
+ : ''
+ })`
+ : undefined,
+ segmentsToDrop
+ ? `${pluralIfNeeded(segmentsToDrop, 'segment')} to drop
(${formatBytesCompact(
+ segmentsToDropSize,
+ )})`
+ : undefined,
+ ).join(', ') || 'Empty load/drop queues'
+ );
+}
+
+function aggregateLoadQueueInfos(loadQueueInfos: LoadQueueInfo[]):
LoadQueueInfo {
+ return {
+ segmentsToLoad: sum(loadQueueInfos, s => Number(s.segmentsToLoad) || 0),
+ segmentsToLoadSize: sum(loadQueueInfos, s => Number(s.segmentsToLoadSize)
|| 0),
+ segmentsToDrop: sum(loadQueueInfos, s => Number(s.segmentsToDrop) || 0),
+ segmentsToDropSize: sum(loadQueueInfos, s => Number(s.segmentsToDropSize)
|| 0),
+ expectedLoadTimeMillis: max(loadQueueInfos, s =>
Number(s.expectedLoadTimeMillis) || 0) || 0,
+ };
}
interface WorkerInfo {
@@ -178,7 +201,7 @@ interface WorkerInfo {
}
export class ServicesView extends React.PureComponent<ServicesViewProps,
ServicesViewState> {
- private readonly serviceQueryManager: QueryManager<Capabilities,
ServiceResultRow[]>;
+ private readonly serviceQueryManager: QueryManager<ServicesQuery,
ServiceResultRow[]>;
// Ranking
// coordinator => 8
@@ -218,23 +241,6 @@ ORDER BY
) DESC,
"service" DESC`;
- static async getServices(): Promise<ServiceResultRow[]> {
- const allServiceResp = await
Api.instance.get('/druid/coordinator/v1/servers?simple');
- const allServices = allServiceResp.data;
- return allServices.map((s: any) => {
- return {
- 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),
- curr_size: s.currSize,
- max_size: s.maxSize,
- tls_port: -1,
- };
- });
- }
-
constructor(props: ServicesViewProps) {
super(props);
this.state = {
@@ -246,63 +252,92 @@ ORDER BY
};
this.serviceQueryManager = new QueryManager({
- processQuery: async capabilities => {
+ processQuery: async ({ capabilities, visibleColumns }, cancelToken) => {
let services: ServiceResultRow[];
if (capabilities.hasSql()) {
- services = await queryDruidSql({ query: ServicesView.SERVICE_SQL });
+ services = await queryDruidSql({ query: ServicesView.SERVICE_SQL },
cancelToken);
} else if (capabilities.hasCoordinatorAccess()) {
- services = await ServicesView.getServices();
+ services = (
+ await Api.instance.get('/druid/coordinator/v1/servers?simple', {
cancelToken })
+ ).data.map((s: any): ServiceResultRow => {
+ const hostParts = s.host.split(':');
+ const port = parseInt(hostParts[1], 10);
+ return {
+ service: s.host,
+ service_type: s.type === 'indexer-executor' ? 'peon' : s.type,
+ tier: s.tier,
+ host: hostParts[0],
+ plaintext_port: port < 9000 ? port : -1,
+ tls_port: port < 9000 ? -1 : port,
+ curr_size: s.currSize,
+ max_size: s.maxSize,
+ start_time: '1970:01:01T00:00:00Z',
+ is_leader: 0,
+ };
+ });
} else {
throw new Error(`must have SQL or coordinator access`);
}
- if (capabilities.hasCoordinatorAccess()) {
- try {
- const loadQueueInfos = (
- await Api.instance.get<Record<string, LoadQueueInfo>>(
- '/druid/coordinator/v1/loadqueue?simple',
- )
- ).data;
- services.forEach(s => {
- s.loadQueueInfo = loadQueueInfos[s.service];
- });
- } catch {
- AppToaster.show({
- icon: IconNames.ERROR,
- intent: Intent.DANGER,
- message: 'There was an error getting the load queue info',
- });
- }
- }
+ const auxiliaryQueries: AuxiliaryQueryFn<ServiceResultRow[]>[] = [];
- if (capabilities.hasOverlordAccess()) {
- try {
- const workerInfos = (await
Api.instance.get<WorkerInfo[]>('/druid/indexer/v1/workers'))
- .data;
-
- const workerInfoLookup: Record<string, WorkerInfo> = lookupBy(
- workerInfos,
- m => m.worker?.host,
- );
-
- services.forEach(s => {
- s.workerInfo = workerInfoLookup[s.service];
- });
- } catch (e) {
- // Swallow this error because it simply a reflection of a local
task runner.
- if (
- deepGet(e, 'response.data.error') !== 'Task Runner does not
support worker listing'
- ) {
+ if (capabilities.hasCoordinatorAccess() &&
visibleColumns.shown('Details')) {
+ auxiliaryQueries.push(async (services, cancelToken) => {
+ try {
+ const loadQueueInfos = (
+ await Api.instance.get<Record<string, LoadQueueInfo>>(
+ '/druid/coordinator/v1/loadqueue?simple',
+ { cancelToken },
+ )
+ ).data;
+ return services.map(s => ({
+ ...s,
+ loadQueueInfo: loadQueueInfos[s.service],
+ }));
+ } catch {
AppToaster.show({
icon: IconNames.ERROR,
intent: Intent.DANGER,
- message: 'There was an error getting the worker info',
+ message: 'There was an error getting the load queue info',
});
+ return services;
}
- }
+ });
}
- return services;
+ if (capabilities.hasOverlordAccess()) {
+ auxiliaryQueries.push(async (services, cancelToken) => {
+ try {
+ const workerInfos = (
+ await
Api.instance.get<WorkerInfo[]>('/druid/indexer/v1/workers', { cancelToken })
+ ).data;
+
+ const workerInfoLookup: Record<string, WorkerInfo> = lookupBy(
+ workerInfos,
+ m => m.worker?.host,
+ );
+
+ return services.map(s => ({
+ ...s,
+ workerInfo: workerInfoLookup[s.service],
+ }));
+ } catch (e) {
+ // Swallow this error because it simply a reflection of a local
task runner.
+ if (
+ deepGet(e, 'response.data.error') !== 'Task Runner does not
support worker listing'
+ ) {
+ AppToaster.show({
+ icon: IconNames.ERROR,
+ intent: Intent.DANGER,
+ message: 'There was an error getting the worker info',
+ });
+ }
+ return services;
+ }
+ });
+ }
+
+ return new ResultWithAuxiliaryWork(services, auxiliaryQueries);
},
onStateChange: servicesState => {
this.setState({
@@ -314,7 +349,8 @@ ORDER BY
componentDidMount(): void {
const { capabilities } = this.props;
- this.serviceQueryManager.runQuery(capabilities);
+ const { visibleColumns } = this.state;
+ this.serviceQueryManager.runQuery({ capabilities, visibleColumns });
}
componentWillUnmount(): void {
@@ -490,10 +526,7 @@ ORDER BY
)
) {
const workerInfos: WorkerInfo[] = filterMap(originalRows, r =>
r.workerInfo);
-
- if (!workerInfos.length) {
- return 'Could not get worker infos';
- }
+ if (!workerInfos.length) return '';
const totalCurrCapacityUsed = sum(
workerInfos,
@@ -517,9 +550,7 @@ ORDER BY
case 'indexer':
case 'middle_manager': {
- if (!deepGet(original, 'workerInfo')) {
- return 'Could not get capacity info';
- }
+ if (!deepGet(original, 'workerInfo')) return '';
const currCapacityUsed = deepGet(original,
'workerInfo.currCapacityUsed') || 0;
const capacity = deepGet(original,
'workerInfo.worker.capacity');
if (typeof capacity === 'number') {
@@ -554,18 +585,24 @@ ORDER BY
case 'middle_manager':
case 'indexer': {
const { workerInfo } = row;
- if (!workerInfo) {
- return 'Could not get detail info';
- }
+ if (!workerInfo) return '';
if (workerInfo.worker.version === '') return 'Disabled';
const details: string[] = [];
if (workerInfo.lastCompletedTaskTime) {
- details.push(`Last completed task:
${workerInfo.lastCompletedTaskTime}`);
+ details.push(
+ `Last completed task:
${prettyFormatIsoDateWithMsIfNeeded(
+ workerInfo.lastCompletedTaskTime,
+ )}`,
+ );
}
if (workerInfo.blacklistedUntil) {
- details.push(`Blacklisted until:
${workerInfo.blacklistedUntil}`);
+ details.push(
+ `Blacklisted until: ${prettyFormatIsoDateWithMsIfNeeded(
+ workerInfo.blacklistedUntil,
+ )}`,
+ );
}
return details.join(' ');
}
@@ -599,16 +636,7 @@ ORDER BY
case 'historical': {
const { loadQueueInfo } = original;
- if (!loadQueueInfo) return 'Could not get load queue info';
-
- const { segmentsToLoad, segmentsToLoadSize, segmentsToDrop,
segmentsToDropSize } =
- loadQueueInfo;
- return formatQueues(
- segmentsToLoad,
- segmentsToLoadSize,
- segmentsToDrop,
- segmentsToDropSize,
- );
+ return loadQueueInfo ? formatLoadQueueInfo(loadQueueInfo) :
'';
}
default:
@@ -618,29 +646,10 @@ ORDER BY
Aggregated: ({ subRows }) => {
const originalRows = subRows.map(r => r._original);
if (!originalRows.some(r => r.service_type === 'historical'))
return '';
-
const loadQueueInfos: LoadQueueInfo[] = filterMap(originalRows,
r => r.loadQueueInfo);
-
- if (!loadQueueInfos.length) {
- return 'Could not get load queue infos';
- }
-
- const segmentsToLoad = sum(loadQueueInfos, s =>
Number(s.segmentsToLoad) || 0);
- const segmentsToLoadSize = sum(
- loadQueueInfos,
- s => Number(s.segmentsToLoadSize) || 0,
- );
- const segmentsToDrop = sum(loadQueueInfos, s =>
Number(s.segmentsToDrop) || 0);
- const segmentsToDropSize = sum(
- loadQueueInfos,
- s => Number(s.segmentsToDropSize) || 0,
- );
- return formatQueues(
- segmentsToLoad,
- segmentsToLoadSize,
- segmentsToDrop,
- segmentsToDropSize,
- );
+ return loadQueueInfos.length
+ ? formatLoadQueueInfo(aggregateLoadQueueInfos(loadQueueInfos))
+ : '';
},
},
{
diff --git
a/web-console/src/views/sql-data-loader-view/schema-step/schema-step.tsx
b/web-console/src/views/sql-data-loader-view/schema-step/schema-step.tsx
index f083a1dbee8..acc20efe02c 100644
--- a/web-console/src/views/sql-data-loader-view/schema-step/schema-step.tsx
+++ b/web-console/src/views/sql-data-loader-view/schema-step/schema-step.tsx
@@ -407,12 +407,15 @@ export const SchemaStep = function SchemaStep(props:
SchemaStepProps) {
const [existingTableState] = useQueryManager<string, string[]>({
initQuery: '',
- processQuery: async (_: string, _cancelToken) => {
+ processQuery: async (_, cancelToken) => {
// Check if datasource already exists
- const tables = await queryDruidSql({
- query: `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE
TABLE_SCHEMA = 'druid' ORDER BY TABLE_NAME ASC`,
- resultFormat: 'array',
- });
+ const tables = await queryDruidSql(
+ {
+ query: `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE
TABLE_SCHEMA = 'druid' ORDER BY TABLE_NAME ASC`,
+ resultFormat: 'array',
+ },
+ cancelToken,
+ );
return tables.map(t => t[0]);
},
@@ -426,7 +429,7 @@ export const SchemaStep = function SchemaStep(props:
SchemaStepProps) {
const [sampleState] = useQueryManager<ExternalConfig, QueryResult,
Execution>({
query: sampleExternalConfig,
- processQuery: async sampleExternalConfig => {
+ processQuery: async (sampleExternalConfig, cancelToken) => {
const sampleResponse = await postToSampler(
{
type: 'index_parallel',
@@ -469,6 +472,7 @@ export const SchemaStep = function SchemaStep(props:
SchemaStepProps) {
},
},
'sample',
+ cancelToken,
);
const columns = getHeaderFromSampleResponse(sampleResponse).map(({ name,
type }) => {
diff --git a/web-console/src/views/tasks-view/tasks-view.tsx
b/web-console/src/views/tasks-view/tasks-view.tsx
index c5a618c4760..90af042702a 100644
--- a/web-console/src/views/tasks-view/tasks-view.tsx
+++ b/web-console/src/views/tasks-view/tasks-view.tsx
@@ -165,13 +165,16 @@ ORDER BY
};
this.taskQueryManager = new QueryManager({
- processQuery: async capabilities => {
+ processQuery: async (capabilities, cancelToken) => {
if (capabilities.hasSql()) {
- return await queryDruidSql({
- query: TasksView.TASK_SQL,
- });
+ return await queryDruidSql(
+ {
+ query: TasksView.TASK_SQL,
+ },
+ cancelToken,
+ );
} else if (capabilities.hasOverlordAccess()) {
- const resp = await Api.instance.get(`/druid/indexer/v1/tasks`);
+ const resp = await Api.instance.get(`/druid/indexer/v1/tasks`, {
cancelToken });
return TasksView.parseTasks(resp.data);
} else {
throw new Error(`must have SQL or overlord access`);
diff --git
a/web-console/src/views/workbench-view/current-dart-panel/current-dart-panel.tsx
b/web-console/src/views/workbench-view/current-dart-panel/current-dart-panel.tsx
index 5b4ac4f316f..321ad40fc8f 100644
---
a/web-console/src/views/workbench-view/current-dart-panel/current-dart-panel.tsx
+++
b/web-console/src/views/workbench-view/current-dart-panel/current-dart-panel.tsx
@@ -66,8 +66,8 @@ export const CurrentDartPanel = React.memo(function
CurrentViberPanel(
const [dartQueryEntriesState, queryManager] = useQueryManager<number,
DartQueryEntry[]>({
query: workStateVersion,
- processQuery: async _ => {
- return (await Api.instance.get('/druid/v2/sql/dart')).data.queries;
+ processQuery: async (_, cancelToken) => {
+ return (await Api.instance.get('/druid/v2/sql/dart', { cancelToken
})).data.queries;
},
});
diff --git
a/web-console/src/views/workbench-view/execution-details-pane-loader/execution-details-pane-loader.tsx
b/web-console/src/views/workbench-view/execution-details-pane-loader/execution-details-pane-loader.tsx
index c83262e37b7..69c84e57d35 100644
---
a/web-console/src/views/workbench-view/execution-details-pane-loader/execution-details-pane-loader.tsx
+++
b/web-console/src/views/workbench-view/execution-details-pane-loader/execution-details-pane-loader.tsx
@@ -39,11 +39,11 @@ export const ExecutionDetailsPaneLoader =
React.memo(function ExecutionDetailsPa
const { id, initTab, initExecution, goToTask } = props;
const [executionState, queryManager] = useQueryManager<string, Execution>({
- processQuery: (id: string) => {
- return getTaskExecution(id);
- },
initQuery: initExecution ? undefined : id,
initState: initExecution ? new QueryState({ data: initExecution }) :
undefined,
+ processQuery: (id, cancelToken) => {
+ return getTaskExecution(id, undefined, cancelToken);
+ },
});
useInterval(() => {
diff --git
a/web-console/src/views/workbench-view/execution-stages-pane-loader/execution-stages-pane-loader.tsx
b/web-console/src/views/workbench-view/execution-stages-pane-loader/execution-stages-pane-loader.tsx
index dc6acf4452c..37dbb0b3577 100644
---
a/web-console/src/views/workbench-view/execution-stages-pane-loader/execution-stages-pane-loader.tsx
+++
b/web-console/src/views/workbench-view/execution-stages-pane-loader/execution-stages-pane-loader.tsx
@@ -35,10 +35,10 @@ export const ExecutionStagesPaneLoader =
React.memo(function ExecutionStagesPane
const { id, goToTask } = props;
const [executionState, queryManager] = useQueryManager<string, Execution>({
- processQuery: (id: string) => {
- return getTaskExecution(id);
- },
initQuery: id,
+ processQuery: (id, cancelToken) => {
+ return getTaskExecution(id, undefined, cancelToken);
+ },
});
useInterval(() => {
diff --git
a/web-console/src/views/workbench-view/explain-dialog/explain-dialog.tsx
b/web-console/src/views/workbench-view/explain-dialog/explain-dialog.tsx
index 3f9c3ea9a3d..9b81edd6d83 100644
--- a/web-console/src/views/workbench-view/explain-dialog/explain-dialog.tsx
+++ b/web-console/src/views/workbench-view/explain-dialog/explain-dialog.tsx
@@ -75,7 +75,8 @@ export const ExplainDialog = React.memo(function
ExplainDialog(props: ExplainDia
const { queryWithContext, onClose, openQueryLabel, onOpenQuery,
mandatoryQueryContext } = props;
const [explainState] = useQueryManager<QueryContextEngine,
QueryExplanation[] | string>({
- processQuery: async queryWithContext => {
+ initQuery: queryWithContext,
+ processQuery: async (queryWithContext, cancelToken) => {
const { engine, queryString, queryContext, wrapQueryLimit } =
queryWithContext;
let context: QueryContext | undefined;
@@ -98,19 +99,19 @@ export const ExplainDialog = React.memo(function
ExplainDialog(props: ExplainDia
let result: any[];
switch (engine) {
case 'sql-native':
- result = await queryDruidSql(payload);
+ result = await queryDruidSql(payload, cancelToken);
break;
case 'sql-msq-task':
try {
- result = (await Api.instance.post(`/druid/v2/sql/task`,
payload)).data;
+ result = (await Api.instance.post(`/druid/v2/sql/task`, payload, {
cancelToken })).data;
} catch (e) {
throw new Error(getDruidErrorMessage(e));
}
break;
case 'sql-msq-dart':
- result = await queryDruidSqlDart(payload);
+ result = await queryDruidSqlDart(payload, cancelToken);
break;
default:
@@ -128,7 +129,6 @@ export const ExplainDialog = React.memo(function
ExplainDialog(props: ExplainDia
return plan;
}
},
- initQuery: queryWithContext,
});
let content: JSX.Element;
diff --git
a/web-console/src/views/workbench-view/input-format-step/input-format-step.tsx
b/web-console/src/views/workbench-view/input-format-step/input-format-step.tsx
index c0a29f5ba1c..4a05b36d70a 100644
---
a/web-console/src/views/workbench-view/input-format-step/input-format-step.tsx
+++
b/web-console/src/views/workbench-view/input-format-step/input-format-step.tsx
@@ -98,7 +98,7 @@ export const InputFormatStep = React.memo(function
InputFormatStep(props: InputF
const [previewState] = useQueryManager<InputSourceAndFormat,
SampleResponse>({
query: inputSourceAndFormatToSample,
- processQuery: async ({ inputSource, inputFormat }) => {
+ processQuery: async ({ inputSource, inputFormat }, cancelToken) => {
const fixedFormatSource = inputSource.type === 'delta';
if (!fixedFormatSource && !isValidInputFormat(inputFormat))
throw new Error('invalid input format');
@@ -131,7 +131,7 @@ export const InputFormatStep = React.memo(function
InputFormatStep(props: InputF
},
};
- return await postToSampler(sampleSpec, 'input-format-step');
+ return await postToSampler(sampleSpec, 'input-format-step', cancelToken);
},
});
diff --git
a/web-console/src/views/workbench-view/recent-query-task-panel/recent-query-task-panel.tsx
b/web-console/src/views/workbench-view/recent-query-task-panel/recent-query-task-panel.tsx
index 4cbc7cc91b5..682f5fd4d38 100644
---
a/web-console/src/views/workbench-view/recent-query-task-panel/recent-query-task-panel.tsx
+++
b/web-console/src/views/workbench-view/recent-query-task-panel/recent-query-task-panel.tsx
@@ -90,9 +90,10 @@ export const RecentQueryTaskPanel = React.memo(function
RecentQueryTaskPanel(
const [queryTaskHistoryState, queryManager] = useQueryManager<number,
RecentQueryEntry[]>({
query: workStateVersion,
- processQuery: async _ => {
- return await queryDruidSql<RecentQueryEntry>({
- query: `SELECT
+ processQuery: async (_, cancelToken) => {
+ return await queryDruidSql<RecentQueryEntry>(
+ {
+ query: `SELECT
CASE WHEN ${TASK_CANCELED_PREDICATE} THEN 'CANCELED' ELSE "status" END AS
"taskStatus",
"task_id" AS "taskId",
"datasource",
@@ -103,7 +104,9 @@ FROM sys.tasks
WHERE "type" = 'query_controller'
ORDER BY "created_time" DESC
LIMIT 100`,
- });
+ },
+ cancelToken,
+ );
},
});
diff --git a/web-console/src/views/workbench-view/workbench-view.tsx
b/web-console/src/views/workbench-view/workbench-view.tsx
index 5250373e81b..106fa3bff7a 100644
--- a/web-console/src/views/workbench-view/workbench-view.tsx
+++ b/web-console/src/views/workbench-view/workbench-view.tsx
@@ -212,10 +212,13 @@ export class WorkbenchView extends
React.PureComponent<WorkbenchViewProps, Workb
componentDidMount(): void {
this.metadataQueryManager = new QueryManager({
- processQuery: async () => {
- return await queryDruidSql<ColumnMetadata>({
- query: `SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE FROM
INFORMATION_SCHEMA.COLUMNS`,
- });
+ processQuery: async (_, cancelToken) => {
+ return await queryDruidSql<ColumnMetadata>(
+ {
+ query: `SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS`,
+ },
+ cancelToken,
+ );
},
onStateChange: columnMetadataState => {
if (columnMetadataState.error) {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]