From de72bb1cf4ede1588fcbcc5d0af6539ce85f6597 Mon Sep 17 00:00:00 2001
From: Vadim Ogievetsky
Date: Wed, 23 Sep 2020 18:40:38 -0700
Subject: [PATCH 1/5] init compaction status
---
.../views/datasource-view/datasource-view.tsx | 52 ++++++++++++++++++-
1 file changed, 51 insertions(+), 1 deletion(-)
diff --git a/web-console/src/views/datasource-view/datasource-view.tsx b/web-console/src/views/datasource-view/datasource-view.tsx
index 20a38e73aeed..5e72eac2d06f 100644
--- a/web-console/src/views/datasource-view/datasource-view.tsx
+++ b/web-console/src/views/datasource-view/datasource-view.tsx
@@ -78,6 +78,7 @@ const tableColumns: Record = {
'Avg. row size',
'Replicated size',
'Compaction',
+ 'Compaction status',
'Retention',
ACTION_COLUMN_LABEL,
],
@@ -88,6 +89,7 @@ const tableColumns: Record = {
'Total data size',
'Segment size',
'Compaction',
+ 'Compaction status',
'Retention',
ACTION_COLUMN_LABEL,
],
@@ -131,9 +133,25 @@ function twoLines(line1: string, line2: string) {
);
}
+interface CompactionStatus {
+ dataSource: string;
+ scheduleStatus: string;
+ bytesAwaitingCompaction: number;
+ bytesCompacted: number;
+ bytesSkipped: number;
+ segmentCountAwaitingCompaction: number;
+ segmentCountCompacted: number;
+ segmentCountSkipped: number;
+ intervalCountAwaitingCompaction: number;
+ intervalCountCompacted: number;
+ intervalCountSkipped: number;
+}
+
interface Datasource {
datasource: string;
rules: Rule[];
+ compaction: Record;
+ compactionStatus: CompactionStatus;
[key: string]: any;
}
@@ -335,12 +353,19 @@ GROUP BY 1`;
(c: any) => c.dataSource,
);
+ const compactionStatusesResp = await axios.get('/druid/coordinator/v1/compaction/status');
+ const compactionStatuses = lookupBy(
+ compactionStatusesResp.data.latestStatus || [],
+ (c: any) => c.dataSource,
+ );
+
const allDatasources = (datasources as any).concat(
unused.map(d => ({ datasource: d, unused: true })),
);
- allDatasources.forEach((ds: any) => {
+ allDatasources.forEach((ds: Datasource) => {
ds.rules = rules[ds.datasource] || [];
ds.compaction = compaction[ds.datasource];
+ ds.compactionStatus = compactionStatuses[ds.datasource];
});
return {
@@ -1030,6 +1055,31 @@ GROUP BY 1`;
);
},
},
+ {
+ Header: twoLines('Compaction', 'status'),
+ show:
+ capabilities.hasCoordinatorAccess() && hiddenColumns.exists('Compaction status'),
+ id: 'compactionStatus',
+ accessor: row => Boolean(row.compactionStatus),
+ filterable: false,
+ Cell: row => {
+ const compactionStatus: CompactionStatus = row.original.compactionStatus;
+ let text: string;
+ if (compactionStatus) {
+ const progress =
+ (compactionStatus.bytesCompacted /
+ (compactionStatus.bytesAwaitingCompaction +
+ compactionStatus.bytesCompacted)) *
+ 100;
+ text = `${compactionStatus.scheduleStatus} (${progress.toFixed(
+ 2,
+ )}%) [${formatBytes(compactionStatus.bytesAwaitingCompaction)}]`;
+ } else {
+ text = 'Not enabled';
+ }
+ return text;
+ },
+ },
{
Header: 'Retention',
show: capabilities.hasCoordinatorAccess() && hiddenColumns.exists('Retention'),
From 55cfcb14dbc0ebde040dd5507257de1babb33d85 Mon Sep 17 00:00:00 2001
From: Vadim Ogievetsky
Date: Fri, 25 Sep 2020 08:00:13 -0700
Subject: [PATCH 2/5] % compacted
---
.../components/more-button/more-button.tsx | 23 ++-
.../compaction-dialog/compaction-dialog.tsx | 2 -
web-console/src/utils/general.tsx | 4 +
.../views/datasource-view/datasource-view.tsx | 141 +++++++++++++-----
4 files changed, 127 insertions(+), 43 deletions(-)
diff --git a/web-console/src/components/more-button/more-button.tsx b/web-console/src/components/more-button/more-button.tsx
index 7a161b7e4977..4bcef07225f9 100644
--- a/web-console/src/components/more-button/more-button.tsx
+++ b/web-console/src/components/more-button/more-button.tsx
@@ -18,14 +18,19 @@
import { Button, Menu, Popover, Position } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
-import React from 'react';
+import React, { useState } from 'react';
+
+type OpenState = 'open' | 'alt-open';
export interface MoreButtonProps {
- children: React.ReactNode;
+ children: React.ReactNode | React.ReactNode[];
+ altExtra?: React.ReactNode;
}
export const MoreButton = React.memo(function MoreButton(props: MoreButtonProps) {
- const { children } = props;
+ const { children, altExtra } = props;
+
+ const [openState, setOpenState] = useState();
let childCount = 0;
// Sadly React.Children.count does not ignore nulls correctly
@@ -36,8 +41,18 @@ export const MoreButton = React.memo(function MoreButton(props: MoreButtonProps)
return (
{children}}
+ isOpen={Boolean(openState)}
+ content={
+
+ }
position={Position.BOTTOM_LEFT}
+ onInteraction={(nextOpenState, e: any) => {
+ if (!e) return; // For some reason this function is always called twice once with e and once without
+ setOpenState(nextOpenState ? (e.altKey ? 'alt-open' : 'open') : undefined);
+ }}
>
diff --git a/web-console/src/dialogs/compaction-dialog/compaction-dialog.tsx b/web-console/src/dialogs/compaction-dialog/compaction-dialog.tsx
index 1798565a2f87..a081f281ac7f 100644
--- a/web-console/src/dialogs/compaction-dialog/compaction-dialog.tsx
+++ b/web-console/src/dialogs/compaction-dialog/compaction-dialog.tsx
@@ -24,8 +24,6 @@ import { deepGet, deepSet } from '../../utils/object-change';
import './compaction-dialog.scss';
-export const DEFAULT_MAX_ROWS_PER_SEGMENT = 5000000;
-
type Tabs = 'form' | 'json';
type CompactionConfig = Record;
diff --git a/web-console/src/utils/general.tsx b/web-console/src/utils/general.tsx
index 47c7e17c9360..7afe3856303c 100644
--- a/web-console/src/utils/general.tsx
+++ b/web-console/src/utils/general.tsx
@@ -231,6 +231,10 @@ export function formatMegabytes(n: number): string {
return numeral(n / 1048576).format('0,0.0');
}
+export function formatPercent(n: number): string {
+ return (n * 100).toFixed(2) + '%';
+}
+
function pad2(str: string | number): string {
return ('00' + str).substr(-2);
}
diff --git a/web-console/src/views/datasource-view/datasource-view.tsx b/web-console/src/views/datasource-view/datasource-view.tsx
index 5e72eac2d06f..77fc9d190409 100644
--- a/web-console/src/views/datasource-view/datasource-view.tsx
+++ b/web-console/src/views/datasource-view/datasource-view.tsx
@@ -37,12 +37,7 @@ import {
TableColumnSelector,
ViewControlBar,
} from '../../components';
-import {
- AsyncActionDialog,
- CompactionDialog,
- DEFAULT_MAX_ROWS_PER_SEGMENT,
- RetentionDialog,
-} from '../../dialogs';
+import { AsyncActionDialog, CompactionDialog, RetentionDialog } from '../../dialogs';
import { DatasourceTableActionDialog } from '../../dialogs/datasource-table-action-dialog/datasource-table-action-dialog';
import { AppToaster } from '../../singletons/toaster';
import {
@@ -51,6 +46,7 @@ import {
formatBytes,
formatInteger,
formatMegabytes,
+ formatPercent,
getDruidErrorMessage,
LocalStorageKeys,
lookupBy,
@@ -78,7 +74,7 @@ const tableColumns: Record = {
'Avg. row size',
'Replicated size',
'Compaction',
- 'Compaction status',
+ '% Compacted',
'Retention',
ACTION_COLUMN_LABEL,
],
@@ -89,7 +85,7 @@ const tableColumns: Record = {
'Total data size',
'Segment size',
'Compaction',
- 'Compaction status',
+ '% Compacted',
'Retention',
ACTION_COLUMN_LABEL,
],
@@ -133,6 +129,12 @@ function twoLines(line1: string, line2: string) {
);
}
+function progress(done: number, awaiting: number): number {
+ const d = done + awaiting;
+ if (!d) return 0;
+ return done / d;
+}
+
interface CompactionStatus {
dataSource: string;
scheduleStatus: string;
@@ -147,6 +149,20 @@ interface CompactionStatus {
intervalCountSkipped: number;
}
+function zeroCompactionStatus(compactionStatus: CompactionStatus): boolean {
+ return (
+ !compactionStatus.bytesAwaitingCompaction &&
+ !compactionStatus.bytesCompacted &&
+ !compactionStatus.bytesSkipped &&
+ !compactionStatus.segmentCountAwaitingCompaction &&
+ !compactionStatus.segmentCountCompacted &&
+ !compactionStatus.segmentCountSkipped &&
+ !compactionStatus.intervalCountAwaitingCompaction &&
+ !compactionStatus.intervalCountCompacted &&
+ !compactionStatus.intervalCountSkipped
+ );
+}
+
interface Datasource {
datasource: string;
rules: Rule[];
@@ -208,6 +224,7 @@ export interface DatasourcesViewState {
datasourceToMarkSegmentsByIntervalIn?: string;
useUnuseAction: 'use' | 'unuse';
useUnuseInterval: string;
+ showForceCompact: boolean;
hiddenColumns: LocalStorageBackedArray;
showChart: boolean;
chartWidth: number;
@@ -277,6 +294,7 @@ GROUP BY 1`;
showUnused: false,
useUnuseAction: 'unuse',
useUnuseInterval: '',
+ showForceCompact: false,
hiddenColumns: new LocalStorageBackedArray(
LocalStorageKeys.DATASOURCE_TABLE_COLUMN_SELECTION,
),
@@ -560,7 +578,18 @@ GROUP BY 1`;
const { goToQuery, capabilities } = this.props;
return (
-
+ {
+ this.setState({ showForceCompact: true });
+ }}
+ />
+ }
+ >
{capabilities.hasSql() && (
,
"name": "skipOffsetFromLatest",
+ "suggestions": Array [
+ "PT0H",
+ "PT1H",
+ "P1D",
+ "P3D",
+ ],
"type": "string",
},
Object {
@@ -265,6 +271,12 @@ exports[`CompactionDialog matches snapshot with compactionConfig (hashed partiti
The offset for searching segments to be compacted. Strongly recommended to set for realtime dataSources.
,
"name": "skipOffsetFromLatest",
+ "suggestions": Array [
+ "PT0H",
+ "PT1H",
+ "P1D",
+ "P3D",
+ ],
"type": "string",
},
Object {
@@ -492,6 +504,12 @@ exports[`CompactionDialog matches snapshot with compactionConfig (single_dim par
The offset for searching segments to be compacted. Strongly recommended to set for realtime dataSources.
,
"name": "skipOffsetFromLatest",
+ "suggestions": Array [
+ "PT0H",
+ "PT1H",
+ "P1D",
+ "P3D",
+ ],
"type": "string",
},
Object {
@@ -719,6 +737,12 @@ exports[`CompactionDialog matches snapshot without compactionConfig 1`] = `
The offset for searching segments to be compacted. Strongly recommended to set for realtime dataSources.
,
"name": "skipOffsetFromLatest",
+ "suggestions": Array [
+ "PT0H",
+ "PT1H",
+ "P1D",
+ "P3D",
+ ],
"type": "string",
},
Object {
diff --git a/web-console/src/dialogs/compaction-dialog/compaction-dialog.tsx b/web-console/src/dialogs/compaction-dialog/compaction-dialog.tsx
index a081f281ac7f..9a165fc2ce06 100644
--- a/web-console/src/dialogs/compaction-dialog/compaction-dialog.tsx
+++ b/web-console/src/dialogs/compaction-dialog/compaction-dialog.tsx
@@ -33,6 +33,7 @@ const COMPACTION_CONFIG_FIELDS: Field[] = [
name: 'skipOffsetFromLatest',
type: 'string',
defaultValue: 'P1D',
+ suggestions: ['PT0H', 'PT1H', 'P1D', 'P3D'],
info: (
The offset for searching segments to be compacted. Strongly recommended to set for realtime
diff --git a/web-console/src/views/datasource-view/__snapshots__/datasource-view.spec.tsx.snap b/web-console/src/views/datasource-view/__snapshots__/datasource-view.spec.tsx.snap
index 1d5ce02941f2..2364a8a27693 100755
--- a/web-console/src/views/datasource-view/__snapshots__/datasource-view.spec.tsx.snap
+++ b/web-console/src/views/datasource-view/__snapshots__/datasource-view.spec.tsx.snap
@@ -11,7 +11,20 @@ exports[`data source view matches snapshot 1`] = `
localStorageKey="datasources-refresh-rate"
onRefresh={[Function]}
/>
-
+
+ }
+ >
+ % Compacted
+
+ bytes / segments / intervals
+ ,
+ "accessor": [Function],
+ "filterable": false,
+ "id": "percentCompacted",
+ "show": true,
+ "width": 200,
+ },
+ Object {
+ "Cell": [Function],
+ "Header":
+ Left to be
+
+ compacted
+ ,
+ "accessor": [Function],
+ "filterable": false,
+ "id": "leftToBeCompacted",
"show": true,
+ "width": 100,
},
Object {
"Cell": [Function],
@@ -226,6 +270,7 @@ exports[`data source view matches snapshot 1`] = `
"accessor": [Function],
"filterable": false,
"id": "retention",
+ "minWidth": 100,
"show": true,
},
Object {
diff --git a/web-console/src/views/datasource-view/datasource-view.tsx b/web-console/src/views/datasource-view/datasource-view.tsx
index 77fc9d190409..184f3c5a5cba 100644
--- a/web-console/src/views/datasource-view/datasource-view.tsx
+++ b/web-console/src/views/datasource-view/datasource-view.tsx
@@ -75,6 +75,7 @@ const tableColumns: Record = {
'Replicated size',
'Compaction',
'% Compacted',
+ 'Left to be compacted',
'Retention',
ACTION_COLUMN_LABEL,
],
@@ -86,6 +87,7 @@ const tableColumns: Record = {
'Segment size',
'Compaction',
'% Compacted',
+ 'Left to be compacted',
'Retention',
ACTION_COLUMN_LABEL,
],
@@ -105,10 +107,10 @@ const tableColumns: Record = {
function formatLoadDrop(segmentsToLoad: number, segmentsToDrop: number): string {
const loadDrop: string[] = [];
if (segmentsToLoad) {
- loadDrop.push(`${segmentsToLoad} segments to load`);
+ loadDrop.push(`${pluralIfNeeded(segmentsToLoad, 'segment')} to load`);
}
if (segmentsToDrop) {
- loadDrop.push(`${segmentsToDrop} segments to drop`);
+ loadDrop.push(`${pluralIfNeeded(segmentsToDrop, 'segment')} to drop`);
}
return loadDrop.join(', ') || 'No segments to load/drop';
}
@@ -118,6 +120,7 @@ const formatSegmentSize = formatMegabytes;
const formatTotalRows = formatInteger;
const formatAvgRowSize = formatInteger;
const formatReplicatedSize = formatBytes;
+const formatLeftToBeCompacted = formatBytes;
function twoLines(line1: string, line2: string) {
return (
@@ -135,6 +138,12 @@ function progress(done: number, awaiting: number): number {
return done / d;
}
+function capitalizeFirst(str: string): string {
+ return str.slice(0, 1).toUpperCase() + str.slice(1).toLowerCase();
+}
+
+const PERCENT_BRACES = ['100.00%'];
+
interface CompactionStatus {
dataSource: string;
scheduleStatus: string;
@@ -163,11 +172,30 @@ function zeroCompactionStatus(compactionStatus: CompactionStatus): boolean {
);
}
+type CompactionConfig = Record;
+
+function formatCompactionConfigAndStatus(
+ compactionConfig: CompactionConfig | undefined,
+ compactionStatus: CompactionStatus | undefined,
+) {
+ if (compactionStatus) {
+ if (compactionStatus.bytesAwaitingCompaction === 0 && !zeroCompactionStatus(compactionStatus)) {
+ return 'Fully compacted';
+ } else {
+ return capitalizeFirst(compactionStatus.scheduleStatus);
+ }
+ } else if (compactionConfig) {
+ return 'Awaiting first run';
+ } else {
+ return 'Not enabled';
+ }
+}
+
interface Datasource {
datasource: string;
rules: Rule[];
- compaction: Record;
- compactionStatus: CompactionStatus;
+ compactionConfig?: CompactionConfig;
+ compactionStatus?: CompactionStatus;
[key: string]: any;
}
@@ -198,7 +226,7 @@ interface RetentionDialogOpenOn {
interface CompactionDialogOpenOn {
datasource: string;
- compactionConfig: Record;
+ compactionConfig: CompactionConfig;
}
export interface DatasourcesViewProps {
@@ -264,7 +292,7 @@ export class DatasourcesView extends React.PureComponent<
FROM sys.segments
GROUP BY 1`;
- static formatRules(rules: any[]): string {
+ static formatRules(rules: Rule[]): string {
if (rules.length === 0) {
return 'No rules';
} else if (rules.length <= 2) {
@@ -350,7 +378,7 @@ GROUP BY 1`;
};
}
- const seen = countBy(datasources, (x: any) => x.datasource);
+ const seen = countBy(datasources, x => x.datasource);
let unused: string[] = [];
if (this.state.showUnused) {
@@ -365,16 +393,16 @@ GROUP BY 1`;
const rulesResp = await axios.get('/druid/coordinator/v1/rules');
const rules = rulesResp.data;
- const compactionResp = await axios.get('/druid/coordinator/v1/config/compaction');
- const compaction = lookupBy(
- compactionResp.data.compactionConfigs,
- (c: any) => c.dataSource,
+ const compactionConfigsResp = await axios.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 compactionStatuses = lookupBy(
compactionStatusesResp.data.latestStatus || [],
- (c: any) => c.dataSource,
+ (c: CompactionStatus) => c.dataSource,
);
const allDatasources = (datasources as any).concat(
@@ -382,7 +410,7 @@ GROUP BY 1`;
);
allDatasources.forEach((ds: Datasource) => {
ds.rules = rules[ds.datasource] || [];
- ds.compaction = compaction[ds.datasource];
+ ds.compactionConfig = compactionConfigs[ds.datasource];
ds.compactionStatus = compactionStatuses[ds.datasource];
});
@@ -631,7 +659,7 @@ GROUP BY 1`;
);
}
- private saveRules = async (datasource: string, rules: any[], comment: string) => {
+ private saveRules = async (datasource: string, rules: Rule[], comment: string) => {
try {
await axios.post(`/druid/coordinator/v1/rules/${datasource}`, rules, {
headers: {
@@ -721,8 +749,8 @@ GROUP BY 1`;
getDatasourceActions(
datasource: string,
unused: boolean,
- rules: any[],
- compactionConfig: Record,
+ rules: Rule[],
+ compactionConfig: CompactionConfig,
): BasicAction[] {
const { goToQuery, goToTask, capabilities } = this.props;
@@ -900,6 +928,12 @@ GROUP BY 1`;
const replicatedSizeValues = datasources.map(d => formatReplicatedSize(d.replicated_size));
+ const leftToBeCompactedValues = datasources.map(d =>
+ d.compactionStatus
+ ? formatLeftToBeCompacted(d.compactionStatus.bytesAwaitingCompaction)
+ : '-',
+ );
+
return (
<>
{
- const value = row.value;
+ Cell: ({ value }) => {
return (
{
@@ -941,14 +974,15 @@ GROUP BY 1`;
show: hiddenColumns.exists('Availability'),
id: 'availability',
filterable: false,
+ minWidth: 200,
accessor: row => {
return {
num_available: row.num_available_segments,
num_total: row.num_segments,
};
},
- Cell: row => {
- const { datasource, num_available_segments, num_segments, unused } = row.original;
+ Cell: ({ original }) => {
+ const { datasource, num_available_segments, num_segments, unused } = original;
if (unused) {
return (
@@ -1006,8 +1040,9 @@ GROUP BY 1`;
id: 'load-drop',
accessor: 'num_segments_to_load',
filterable: false,
- Cell: row => {
- const { num_segments_to_load, num_segments_to_drop } = row.original;
+ minWidth: 100,
+ Cell: ({ original }) => {
+ const { num_segments_to_load, num_segments_to_drop } = original;
return formatLoadDrop(num_segments_to_load, num_segments_to_drop);
},
},
@@ -1017,8 +1052,8 @@ GROUP BY 1`;
accessor: 'total_data_size',
filterable: false,
width: 100,
- Cell: row => (
-
+ Cell: ({ value }) => (
+
),
},
{
@@ -1026,18 +1061,18 @@ GROUP BY 1`;
show: hiddenColumns.exists('Segment size'),
accessor: 'avg_segment_size',
filterable: false,
- width: 200,
- Cell: row => (
+ width: 150,
+ Cell: ({ value, original }) => (
<>
{' '}
{' '}
- {' '}
+ {' '}
{' '}
>
@@ -1049,8 +1084,8 @@ GROUP BY 1`;
accessor: 'total_rows',
filterable: false,
width: 100,
- Cell: row => (
-
+ Cell: ({ value }) => (
+
),
},
{
@@ -1059,8 +1094,8 @@ GROUP BY 1`;
accessor: 'avg_row_size',
filterable: false,
width: 100,
- Cell: row => (
-
+ Cell: ({ value }) => (
+
),
},
{
@@ -1069,8 +1104,8 @@ GROUP BY 1`;
accessor: 'replicated_size',
filterable: false,
width: 100,
- Cell: row => (
-
+ Cell: ({ value }) => (
+
),
},
{
@@ -1079,30 +1114,22 @@ GROUP BY 1`;
id: 'compactionStatus',
accessor: row => Boolean(row.compactionStatus),
filterable: false,
- Cell: row => {
- const { compaction } = row.original;
- const compactionStatus: CompactionStatus = row.original.compactionStatus;
- let text: string;
- if (compactionStatus) {
- text = `${compactionStatus.scheduleStatus} [${formatBytes(
- compactionStatus.bytesAwaitingCompaction,
- )}]`;
- } else {
- text = 'Not enabled';
- }
+ width: 150,
+ Cell: ({ original }) => {
+ const { datasource, compactionConfig, compactionStatus } = original;
return (
this.setState({
compactionDialogOpenOn: {
- datasource: row.original.datasource,
- compactionConfig: compaction,
+ datasource,
+ compactionConfig,
},
})
}
>
- {text}
+ {formatCompactionConfigAndStatus(compactionConfig, compactionStatus)}
);
@@ -1112,7 +1139,7 @@ GROUP BY 1`;
Header: twoLines('% Compacted', 'bytes / segments / intervals'),
show: capabilities.hasCoordinatorAccess() && hiddenColumns.exists('% Compacted'),
id: 'percentCompacted',
- width: 180,
+ width: 200,
accessor: ({ compactionStatus }) =>
compactionStatus && compactionStatus.bytesCompacted
? compactionStatus.bytesCompacted /
@@ -1123,27 +1150,72 @@ GROUP BY 1`;
const { compactionStatus } = original;
if (!compactionStatus || zeroCompactionStatus(compactionStatus)) {
- return '-';
+ return (
+ <>
+ {' '}
+ {' '}
+
+ >
+ );
}
- const percentBytes = progress(
- compactionStatus.bytesCompacted,
- compactionStatus.bytesAwaitingCompaction,
+ return (
+ <>
+ {' '}
+ {' '}
+ {' '}
+ {' '}
+
+ >
);
+ },
+ },
+ {
+ Header: twoLines('Left to be', 'compacted'),
+ show:
+ capabilities.hasCoordinatorAccess() && hiddenColumns.exists('Left to be compacted'),
+ id: 'leftToBeCompacted',
+ width: 100,
+ accessor: ({ compactionStatus }) =>
+ (compactionStatus && compactionStatus.bytesAwaitingCompaction) || 0,
+ filterable: false,
+ Cell: ({ original }) => {
+ const { compactionStatus } = original;
- const percentSegments = progress(
- compactionStatus.segmentCountCompacted,
- compactionStatus.segmentCountAwaitingCompaction,
- );
+ if (!compactionStatus) {
+ return ;
+ }
- const percentIntervals = progress(
- compactionStatus.intervalCountCompacted,
- compactionStatus.intervalCountAwaitingCompaction,
+ return (
+
);
-
- return `${formatPercent(percentBytes)} ${formatPercent(
- percentSegments,
- )} ${formatPercent(percentIntervals)}`;
},
},
{
@@ -1152,28 +1224,25 @@ GROUP BY 1`;
id: 'retention',
accessor: row => row.rules.length,
filterable: false,
- Cell: row => {
- const { rules } = row.original;
- let text: string;
- if (rules.length === 0) {
- text = 'Cluster default: ' + DatasourcesView.formatRules(defaultRules);
- } else {
- text = DatasourcesView.formatRules(rules);
- }
-
+ minWidth: 100,
+ Cell: ({ original }) => {
+ const { datasource, rules } = original;
return (
this.setState({
retentionDialogOpenOn: {
- datasource: row.original.datasource,
- rules: row.original.rules,
+ datasource,
+ rules,
},
})
}
className="clickable-cell"
>
- {text}
+ {rules.length
+ ? DatasourcesView.formatRules(rules)
+ : `Cluster default: ${DatasourcesView.formatRules(defaultRules)}`}
+
);
@@ -1186,9 +1255,8 @@ GROUP BY 1`;
id: ACTION_COLUMN_ID,
width: ACTION_COLUMN_WIDTH,
filterable: false,
- Cell: row => {
- const datasource = row.value;
- const { unused, rules, compaction } = row.original;
+ Cell: ({ value: datasource, original }) => {
+ const { unused, rules, compaction } = original;
const datasourceActions = this.getDatasourceActions(
datasource,
unused,
diff --git a/web-console/src/views/query-view/query-input/query-input.tsx b/web-console/src/views/query-view/query-input/query-input.tsx
index 789795312828..12862a3daa98 100644
--- a/web-console/src/views/query-view/query-input/query-input.tsx
+++ b/web-console/src/views/query-view/query-input/query-input.tsx
@@ -214,7 +214,7 @@ export class QueryInput extends React.PureComponent
Date: Mon, 28 Sep 2020 14:28:34 -0700
Subject: [PATCH 4/5] extracted utils, added tests
---
web-console/src/utils/compaction.spec.ts | 87 +++++++++++++++++++
web-console/src/utils/compaction.ts | 68 +++++++++++++++
web-console/src/utils/index.tsx | 1 +
.../views/datasource-view/datasource-view.tsx | 55 +-----------
4 files changed, 160 insertions(+), 51 deletions(-)
create mode 100644 web-console/src/utils/compaction.spec.ts
create mode 100644 web-console/src/utils/compaction.ts
diff --git a/web-console/src/utils/compaction.spec.ts b/web-console/src/utils/compaction.spec.ts
new file mode 100644
index 000000000000..452cfbe48d67
--- /dev/null
+++ b/web-console/src/utils/compaction.spec.ts
@@ -0,0 +1,87 @@
+/*
+ * 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 {
+ CompactionConfig,
+ CompactionStatus,
+ formatCompactionConfigAndStatus,
+ zeroCompactionStatus,
+} from './compaction';
+
+describe('compaction', () => {
+ const BASIC_CONFIG: CompactionConfig = {};
+ const ZERO_STATUS: CompactionStatus = {
+ dataSource: 'tbl',
+ scheduleStatus: 'RUNNING',
+ bytesAwaitingCompaction: 0,
+ bytesCompacted: 0,
+ bytesSkipped: 0,
+ segmentCountAwaitingCompaction: 0,
+ segmentCountCompacted: 0,
+ segmentCountSkipped: 0,
+ intervalCountAwaitingCompaction: 0,
+ intervalCountCompacted: 0,
+ intervalCountSkipped: 0,
+ };
+
+ it('zeroCompactionStatus', () => {
+ expect(zeroCompactionStatus(ZERO_STATUS)).toEqual(true);
+
+ expect(
+ zeroCompactionStatus({
+ dataSource: 'tbl',
+ scheduleStatus: 'RUNNING',
+ bytesAwaitingCompaction: 1,
+ bytesCompacted: 0,
+ bytesSkipped: 0,
+ segmentCountAwaitingCompaction: 0,
+ segmentCountCompacted: 0,
+ segmentCountSkipped: 0,
+ intervalCountAwaitingCompaction: 0,
+ intervalCountCompacted: 0,
+ intervalCountSkipped: 0,
+ }),
+ ).toEqual(false);
+ });
+
+ it('formatCompactionConfigAndStatus', () => {
+ expect(formatCompactionConfigAndStatus(undefined, undefined)).toEqual('Not enabled');
+
+ expect(formatCompactionConfigAndStatus(BASIC_CONFIG, undefined)).toEqual('Awaiting first run');
+
+ expect(formatCompactionConfigAndStatus(undefined, ZERO_STATUS)).toEqual('Running');
+
+ expect(formatCompactionConfigAndStatus(BASIC_CONFIG, ZERO_STATUS)).toEqual('Running');
+
+ expect(
+ formatCompactionConfigAndStatus(BASIC_CONFIG, {
+ dataSource: 'tbl',
+ scheduleStatus: 'RUNNING',
+ bytesAwaitingCompaction: 0,
+ bytesCompacted: 100,
+ bytesSkipped: 0,
+ segmentCountAwaitingCompaction: 0,
+ segmentCountCompacted: 10,
+ segmentCountSkipped: 0,
+ intervalCountAwaitingCompaction: 0,
+ intervalCountCompacted: 10,
+ intervalCountSkipped: 0,
+ }),
+ ).toEqual('Fully compacted');
+ });
+});
diff --git a/web-console/src/utils/compaction.ts b/web-console/src/utils/compaction.ts
new file mode 100644
index 000000000000..34634a1f9b75
--- /dev/null
+++ b/web-console/src/utils/compaction.ts
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+function capitalizeFirst(str: string): string {
+ return str.slice(0, 1).toUpperCase() + str.slice(1).toLowerCase();
+}
+
+export interface CompactionStatus {
+ dataSource: string;
+ scheduleStatus: string;
+ bytesAwaitingCompaction: number;
+ bytesCompacted: number;
+ bytesSkipped: number;
+ segmentCountAwaitingCompaction: number;
+ segmentCountCompacted: number;
+ segmentCountSkipped: number;
+ intervalCountAwaitingCompaction: number;
+ intervalCountCompacted: number;
+ intervalCountSkipped: number;
+}
+
+export type CompactionConfig = Record;
+
+export function zeroCompactionStatus(compactionStatus: CompactionStatus): boolean {
+ return (
+ !compactionStatus.bytesAwaitingCompaction &&
+ !compactionStatus.bytesCompacted &&
+ !compactionStatus.bytesSkipped &&
+ !compactionStatus.segmentCountAwaitingCompaction &&
+ !compactionStatus.segmentCountCompacted &&
+ !compactionStatus.segmentCountSkipped &&
+ !compactionStatus.intervalCountAwaitingCompaction &&
+ !compactionStatus.intervalCountCompacted &&
+ !compactionStatus.intervalCountSkipped
+ );
+}
+
+export function formatCompactionConfigAndStatus(
+ compactionConfig: CompactionConfig | undefined,
+ compactionStatus: CompactionStatus | undefined,
+) {
+ if (compactionStatus) {
+ if (compactionStatus.bytesAwaitingCompaction === 0 && !zeroCompactionStatus(compactionStatus)) {
+ return 'Fully compacted';
+ } else {
+ return capitalizeFirst(compactionStatus.scheduleStatus);
+ }
+ } else if (compactionConfig) {
+ return 'Awaiting first run';
+ } else {
+ return 'Not enabled';
+ }
+}
diff --git a/web-console/src/utils/index.tsx b/web-console/src/utils/index.tsx
index b46d675819fe..2bcf661c7f97 100644
--- a/web-console/src/utils/index.tsx
+++ b/web-console/src/utils/index.tsx
@@ -24,3 +24,4 @@ export * from './query-manager';
export * from './query-cursor';
export * from './local-storage-keys';
export * from './column-metadata';
+export * from './compaction';
diff --git a/web-console/src/views/datasource-view/datasource-view.tsx b/web-console/src/views/datasource-view/datasource-view.tsx
index 184f3c5a5cba..6f693c1bdc9e 100644
--- a/web-console/src/views/datasource-view/datasource-view.tsx
+++ b/web-console/src/views/datasource-view/datasource-view.tsx
@@ -42,8 +42,11 @@ import { DatasourceTableActionDialog } from '../../dialogs/datasource-table-acti
import { AppToaster } from '../../singletons/toaster';
import {
addFilter,
+ CompactionConfig,
+ CompactionStatus,
countBy,
formatBytes,
+ formatCompactionConfigAndStatus,
formatInteger,
formatMegabytes,
formatPercent,
@@ -54,6 +57,7 @@ import {
queryDruidSql,
QueryManager,
QueryState,
+ zeroCompactionStatus,
} from '../../utils';
import { BasicAction } from '../../utils/basic-action';
import { Capabilities, CapabilitiesMode } from '../../utils/capabilities';
@@ -138,59 +142,8 @@ function progress(done: number, awaiting: number): number {
return done / d;
}
-function capitalizeFirst(str: string): string {
- return str.slice(0, 1).toUpperCase() + str.slice(1).toLowerCase();
-}
-
const PERCENT_BRACES = ['100.00%'];
-interface CompactionStatus {
- dataSource: string;
- scheduleStatus: string;
- bytesAwaitingCompaction: number;
- bytesCompacted: number;
- bytesSkipped: number;
- segmentCountAwaitingCompaction: number;
- segmentCountCompacted: number;
- segmentCountSkipped: number;
- intervalCountAwaitingCompaction: number;
- intervalCountCompacted: number;
- intervalCountSkipped: number;
-}
-
-function zeroCompactionStatus(compactionStatus: CompactionStatus): boolean {
- return (
- !compactionStatus.bytesAwaitingCompaction &&
- !compactionStatus.bytesCompacted &&
- !compactionStatus.bytesSkipped &&
- !compactionStatus.segmentCountAwaitingCompaction &&
- !compactionStatus.segmentCountCompacted &&
- !compactionStatus.segmentCountSkipped &&
- !compactionStatus.intervalCountAwaitingCompaction &&
- !compactionStatus.intervalCountCompacted &&
- !compactionStatus.intervalCountSkipped
- );
-}
-
-type CompactionConfig = Record;
-
-function formatCompactionConfigAndStatus(
- compactionConfig: CompactionConfig | undefined,
- compactionStatus: CompactionStatus | undefined,
-) {
- if (compactionStatus) {
- if (compactionStatus.bytesAwaitingCompaction === 0 && !zeroCompactionStatus(compactionStatus)) {
- return 'Fully compacted';
- } else {
- return capitalizeFirst(compactionStatus.scheduleStatus);
- }
- } else if (compactionConfig) {
- return 'Awaiting first run';
- } else {
- return 'Not enabled';
- }
-}
-
interface Datasource {
datasource: string;
rules: Rule[];
From abfb72397a7b7f07808c8ecf457b07a80c9558de Mon Sep 17 00:00:00 2001
From: Vadim Ogievetsky
Date: Mon, 28 Sep 2020 15:42:04 -0700
Subject: [PATCH 5/5] add tests to general foramt functions
---
web-console/src/utils/general.spec.ts | 35 +++++++++++++++++++
.../views/datasource-view/datasource-view.tsx | 2 +-
2 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/web-console/src/utils/general.spec.ts b/web-console/src/utils/general.spec.ts
index 2a327bdb3f23..9b2398bb9ce6 100644
--- a/web-console/src/utils/general.spec.ts
+++ b/web-console/src/utils/general.spec.ts
@@ -18,6 +18,11 @@
import {
alphanumericCompare,
+ formatBytes,
+ formatBytesCompact,
+ formatInteger,
+ formatMegabytes,
+ formatPercent,
sortWithPrefixSuffix,
sqlQueryCustomTableFilter,
swapElements,
@@ -83,4 +88,34 @@ describe('general', () => {
expect(swapElements(array, 2, 4)).toEqual(['a', 'b', 'e', 'd', 'c']);
});
});
+
+ describe('formatInteger', () => {
+ it('works', () => {
+ expect(formatInteger(10000)).toEqual('10,000');
+ });
+ });
+
+ describe('formatBytes', () => {
+ it('works', () => {
+ expect(formatBytes(10000)).toEqual('10.00 KB');
+ });
+ });
+
+ describe('formatBytesCompact', () => {
+ it('works', () => {
+ expect(formatBytesCompact(10000)).toEqual('10.00KB');
+ });
+ });
+
+ describe('formatMegabytes', () => {
+ it('works', () => {
+ expect(formatMegabytes(30000000)).toEqual('28.6');
+ });
+ });
+
+ describe('formatPercent', () => {
+ it('works', () => {
+ expect(formatPercent(2 / 3)).toEqual('66.67%');
+ });
+ });
});
diff --git a/web-console/src/views/datasource-view/datasource-view.tsx b/web-console/src/views/datasource-view/datasource-view.tsx
index 6f693c1bdc9e..59688b04a658 100644
--- a/web-console/src/views/datasource-view/datasource-view.tsx
+++ b/web-console/src/views/datasource-view/datasource-view.tsx
@@ -142,7 +142,7 @@ function progress(done: number, awaiting: number): number {
return done / d;
}
-const PERCENT_BRACES = ['100.00%'];
+const PERCENT_BRACES = [formatPercent(1)];
interface Datasource {
datasource: string;