Web console: Display compaction status (#10438)

* init compaction status

* % compacted

* final UI tweaks

* extracted utils, added tests

* add tests to general foramt functions
This commit is contained in:
Vadim Ogievetsky 2020-09-28 22:19:28 -07:00 committed by GitHub
parent 599aacce0f
commit 729bcba7ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 504 additions and 89 deletions

View File

@ -80,12 +80,11 @@ export const JsonInput = React.memo(function JsonInput(props: JsonInputProps) {
const aceEditor = useRef<Editor | undefined>();
useEffect(() => {
if (!deepEqual(value, internalValue.value)) {
setInternalValue({
value,
stringified: stringifyJson(value),
});
}
if (deepEqual(value, internalValue.value)) return;
setInternalValue({
value,
stringified: stringifyJson(value),
});
}, [value]);
const internalValueError = internalValue.error;
@ -149,8 +148,8 @@ export const JsonInput = React.memo(function JsonInput(props: JsonInputProps) {
const rc = extractRowColumnFromHjsonError(internalValueError);
if (!rc) return;
aceEditor.current.focus(); // Grab the focus
aceEditor.current.getSelection().moveCursorTo(rc.row, rc.column);
aceEditor.current.focus(); // Grab the focus also
}}
>
{internalValueError.message}

View File

@ -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<OpenState | undefined>();
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 (
<Popover
className="more-button"
content={<Menu>{children}</Menu>}
isOpen={Boolean(openState)}
content={
<Menu>
{children}
{openState === 'alt-open' && altExtra}
</Menu>
}
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);
}}
>
<Button icon={IconNames.MORE} disabled={!childCount} />
</Popover>

View File

@ -38,6 +38,12 @@ exports[`CompactionDialog matches snapshot with compactionConfig (dynamic partit
The offset for searching segments to be compacted. Strongly recommended to set for realtime dataSources.
</p>,
"name": "skipOffsetFromLatest",
"suggestions": Array [
"PT0H",
"PT1H",
"P1D",
"P3D",
],
"type": "string",
},
Object {
@ -264,6 +270,12 @@ exports[`CompactionDialog matches snapshot with compactionConfig (hashed partiti
The offset for searching segments to be compacted. Strongly recommended to set for realtime dataSources.
</p>,
"name": "skipOffsetFromLatest",
"suggestions": Array [
"PT0H",
"PT1H",
"P1D",
"P3D",
],
"type": "string",
},
Object {
@ -490,6 +502,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.
</p>,
"name": "skipOffsetFromLatest",
"suggestions": Array [
"PT0H",
"PT1H",
"P1D",
"P3D",
],
"type": "string",
},
Object {
@ -716,6 +734,12 @@ exports[`CompactionDialog matches snapshot without compactionConfig 1`] = `
The offset for searching segments to be compacted. Strongly recommended to set for realtime dataSources.
</p>,
"name": "skipOffsetFromLatest",
"suggestions": Array [
"PT0H",
"PT1H",
"P1D",
"P3D",
],
"type": "string",
},
Object {

View File

@ -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<string, any>;
@ -35,6 +33,7 @@ const COMPACTION_CONFIG_FIELDS: Field<CompactionConfig>[] = [
name: 'skipOffsetFromLatest',
type: 'string',
defaultValue: 'P1D',
suggestions: ['PT0H', 'PT1H', 'P1D', 'P3D'],
info: (
<p>
The offset for searching segments to be compacted. Strongly recommended to set for realtime

View File

@ -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');
});
});

View File

@ -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<string, any>;
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';
}
}

View File

@ -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%');
});
});
});

View File

@ -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);
}

View File

@ -24,3 +24,4 @@ export * from './query-manager';
export * from './query-cursor';
export * from './local-storage-keys';
export * from './column-metadata';
export * from './compaction';

View File

@ -11,7 +11,20 @@ exports[`data source view matches snapshot 1`] = `
localStorageKey="datasources-refresh-rate"
onRefresh={[Function]}
/>
<Memo(MoreButton)>
<Memo(MoreButton)
altExtra={
<Blueprint3.MenuItem
disabled={false}
icon="compressed"
intent="danger"
multiline={false}
onClick={[Function]}
popoverProps={Object {}}
shouldDismissPopover={true}
text="Force compaction run (debug)"
/>
}
>
<Blueprint3.MenuItem
disabled={false}
icon="application"
@ -55,6 +68,8 @@ exports[`data source view matches snapshot 1`] = `
"Avg. row size",
"Replicated size",
"Compaction",
"% Compacted",
"Left to be compacted",
"Retention",
"Actions",
]
@ -137,6 +152,7 @@ exports[`data source view matches snapshot 1`] = `
"accessor": [Function],
"filterable": false,
"id": "availability",
"minWidth": 200,
"show": true,
"sortMethod": [Function],
},
@ -150,6 +166,7 @@ exports[`data source view matches snapshot 1`] = `
"accessor": "num_segments_to_load",
"filterable": false,
"id": "load-drop",
"minWidth": 100,
"show": true,
},
Object {
@ -174,7 +191,7 @@ exports[`data source view matches snapshot 1`] = `
"accessor": "avg_segment_size",
"filterable": false,
"show": true,
"width": 200,
"width": 150,
},
Object {
"Cell": [Function],
@ -217,8 +234,35 @@ exports[`data source view matches snapshot 1`] = `
"Header": "Compaction",
"accessor": [Function],
"filterable": false,
"id": "compaction",
"id": "compactionStatus",
"show": true,
"width": 150,
},
Object {
"Cell": [Function],
"Header": <React.Fragment>
% Compacted
<br />
bytes / segments / intervals
</React.Fragment>,
"accessor": [Function],
"filterable": false,
"id": "percentCompacted",
"show": true,
"width": 200,
},
Object {
"Cell": [Function],
"Header": <React.Fragment>
Left to be
<br />
compacted
</React.Fragment>,
"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 {

View File

@ -37,20 +37,19 @@ 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 {
addFilter,
CompactionConfig,
CompactionStatus,
countBy,
formatBytes,
formatCompactionConfigAndStatus,
formatInteger,
formatMegabytes,
formatPercent,
getDruidErrorMessage,
LocalStorageKeys,
lookupBy,
@ -58,6 +57,7 @@ import {
queryDruidSql,
QueryManager,
QueryState,
zeroCompactionStatus,
} from '../../utils';
import { BasicAction } from '../../utils/basic-action';
import { Capabilities, CapabilitiesMode } from '../../utils/capabilities';
@ -78,6 +78,8 @@ const tableColumns: Record<CapabilitiesMode, string[]> = {
'Avg. row size',
'Replicated size',
'Compaction',
'% Compacted',
'Left to be compacted',
'Retention',
ACTION_COLUMN_LABEL,
],
@ -88,6 +90,8 @@ const tableColumns: Record<CapabilitiesMode, string[]> = {
'Total data size',
'Segment size',
'Compaction',
'% Compacted',
'Left to be compacted',
'Retention',
ACTION_COLUMN_LABEL,
],
@ -107,10 +111,10 @@ const tableColumns: Record<CapabilitiesMode, string[]> = {
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';
}
@ -120,6 +124,7 @@ const formatSegmentSize = formatMegabytes;
const formatTotalRows = formatInteger;
const formatAvgRowSize = formatInteger;
const formatReplicatedSize = formatBytes;
const formatLeftToBeCompacted = formatBytes;
function twoLines(line1: string, line2: string) {
return (
@ -131,9 +136,19 @@ function twoLines(line1: string, line2: string) {
);
}
function progress(done: number, awaiting: number): number {
const d = done + awaiting;
if (!d) return 0;
return done / d;
}
const PERCENT_BRACES = [formatPercent(1)];
interface Datasource {
datasource: string;
rules: Rule[];
compactionConfig?: CompactionConfig;
compactionStatus?: CompactionStatus;
[key: string]: any;
}
@ -164,7 +179,7 @@ interface RetentionDialogOpenOn {
interface CompactionDialogOpenOn {
datasource: string;
compactionConfig: Record<string, any>;
compactionConfig: CompactionConfig;
}
export interface DatasourcesViewProps {
@ -190,6 +205,7 @@ export interface DatasourcesViewState {
datasourceToMarkSegmentsByIntervalIn?: string;
useUnuseAction: 'use' | 'unuse';
useUnuseInterval: string;
showForceCompact: boolean;
hiddenColumns: LocalStorageBackedArray<string>;
showChart: boolean;
chartWidth: number;
@ -229,7 +245,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) {
@ -259,6 +275,7 @@ GROUP BY 1`;
showUnused: false,
useUnuseAction: 'unuse',
useUnuseInterval: '',
showForceCompact: false,
hiddenColumns: new LocalStorageBackedArray<string>(
LocalStorageKeys.DATASOURCE_TABLE_COLUMN_SELECTION,
),
@ -314,7 +331,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) {
@ -329,18 +346,25 @@ 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: CompactionStatus) => 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.compactionConfig = compactionConfigs[ds.datasource];
ds.compactionStatus = compactionStatuses[ds.datasource];
});
return {
@ -535,7 +559,18 @@ GROUP BY 1`;
const { goToQuery, capabilities } = this.props;
return (
<MoreButton>
<MoreButton
altExtra={
<MenuItem
icon={IconNames.COMPRESSED}
text="Force compaction run (debug)"
intent={Intent.DANGER}
onClick={() => {
this.setState({ showForceCompact: true });
}}
/>
}
>
{capabilities.hasSql() && (
<MenuItem
icon={IconNames.APPLICATION}
@ -552,7 +587,32 @@ GROUP BY 1`;
);
}
private saveRules = async (datasource: string, rules: any[], comment: string) => {
renderForceCompactAction() {
const { showForceCompact } = this.state;
if (!showForceCompact) return;
return (
<AsyncActionDialog
action={async () => {
const resp = await axios.post(`/druid/coordinator/v1/compaction/compact`, {});
return resp.data;
}}
confirmButtonText="Force compaction run"
successText="Out of band compaction run has been initiated"
failText="Could not force compaction"
intent={Intent.DANGER}
onClose={() => {
this.setState({ showForceCompact: false });
}}
>
<p>Are you sure you want to force a compaction run?</p>
<p>This functionality only exists for debugging and testing reasons.</p>
<p>If you are running it in production you are doing something wrong.</p>
</AsyncActionDialog>
);
}
private saveRules = async (datasource: string, rules: Rule[], comment: string) => {
try {
await axios.post(`/druid/coordinator/v1/rules/${datasource}`, rules, {
headers: {
@ -642,8 +702,8 @@ GROUP BY 1`;
getDatasourceActions(
datasource: string,
unused: boolean,
rules: any[],
compactionConfig: Record<string, any>,
rules: Rule[],
compactionConfig: CompactionConfig,
): BasicAction[] {
const { goToQuery, goToTask, capabilities } = this.props;
@ -821,6 +881,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 (
<>
<ReactTable
@ -842,8 +908,7 @@ GROUP BY 1`;
show: hiddenColumns.exists('Datasource name'),
accessor: 'datasource',
width: 150,
Cell: row => {
const value = row.value;
Cell: ({ value }) => {
return (
<a
onClick={() => {
@ -862,14 +927,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 (
@ -927,8 +993,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);
},
},
@ -938,8 +1005,8 @@ GROUP BY 1`;
accessor: 'total_data_size',
filterable: false,
width: 100,
Cell: row => (
<BracedText text={formatTotalDataSize(row.value)} braces={totalDataSizeValues} />
Cell: ({ value }) => (
<BracedText text={formatTotalDataSize(value)} braces={totalDataSizeValues} />
),
},
{
@ -947,18 +1014,18 @@ GROUP BY 1`;
show: hiddenColumns.exists('Segment size'),
accessor: 'avg_segment_size',
filterable: false,
width: 200,
Cell: row => (
width: 150,
Cell: ({ value, original }) => (
<>
<BracedText
text={formatSegmentSize(row.original.min_segment_size)}
text={formatSegmentSize(original.min_segment_size)}
braces={minSegmentSizeValues}
/>{' '}
&nbsp;{' '}
<BracedText text={formatSegmentSize(row.value)} braces={avgSegmentSizeValues} />{' '}
<BracedText text={formatSegmentSize(value)} braces={avgSegmentSizeValues} />{' '}
&nbsp;{' '}
<BracedText
text={formatSegmentSize(row.original.max_segment_size)}
text={formatSegmentSize(original.max_segment_size)}
braces={maxSegmentSizeValues}
/>
</>
@ -970,8 +1037,8 @@ GROUP BY 1`;
accessor: 'total_rows',
filterable: false,
width: 100,
Cell: row => (
<BracedText text={formatTotalRows(row.value)} braces={totalRowsValues} />
Cell: ({ value }) => (
<BracedText text={formatTotalRows(value)} braces={totalRowsValues} />
),
},
{
@ -980,8 +1047,8 @@ GROUP BY 1`;
accessor: 'avg_row_size',
filterable: false,
width: 100,
Cell: row => (
<BracedText text={formatAvgRowSize(row.value)} braces={avgRowSizeValues} />
Cell: ({ value }) => (
<BracedText text={formatAvgRowSize(value)} braces={avgRowSizeValues} />
),
},
{
@ -990,74 +1057,145 @@ GROUP BY 1`;
accessor: 'replicated_size',
filterable: false,
width: 100,
Cell: row => (
<BracedText text={formatReplicatedSize(row.value)} braces={replicatedSizeValues} />
Cell: ({ value }) => (
<BracedText text={formatReplicatedSize(value)} braces={replicatedSizeValues} />
),
},
{
Header: 'Compaction',
show: capabilities.hasCoordinatorAccess() && hiddenColumns.exists('Compaction'),
id: 'compaction',
accessor: row => Boolean(row.compaction),
id: 'compactionStatus',
accessor: row => Boolean(row.compactionStatus),
filterable: false,
Cell: row => {
const { compaction } = row.original;
let text: string;
if (compaction) {
if (compaction.maxRowsPerSegment == null) {
text = `Target: Default (${formatInteger(DEFAULT_MAX_ROWS_PER_SEGMENT)})`;
} else {
text = `Target: ${formatInteger(compaction.maxRowsPerSegment)}`;
}
} else {
text = 'Not enabled';
}
width: 150,
Cell: ({ original }) => {
const { datasource, compactionConfig, compactionStatus } = original;
return (
<span
className="clickable-cell"
onClick={() =>
this.setState({
compactionDialogOpenOn: {
datasource: row.original.datasource,
compactionConfig: compaction,
datasource,
compactionConfig,
},
})
}
>
{text}&nbsp;
{formatCompactionConfigAndStatus(compactionConfig, compactionStatus)}&nbsp;
<ActionIcon icon={IconNames.EDIT} />
</span>
);
},
},
{
Header: twoLines('% Compacted', 'bytes / segments / intervals'),
show: capabilities.hasCoordinatorAccess() && hiddenColumns.exists('% Compacted'),
id: 'percentCompacted',
width: 200,
accessor: ({ compactionStatus }) =>
compactionStatus && compactionStatus.bytesCompacted
? compactionStatus.bytesCompacted /
(compactionStatus.bytesAwaitingCompaction + compactionStatus.bytesCompacted)
: 0,
filterable: false,
Cell: ({ original }) => {
const { compactionStatus } = original;
if (!compactionStatus || zeroCompactionStatus(compactionStatus)) {
return (
<>
<BracedText text="-" braces={PERCENT_BRACES} /> &nbsp;{' '}
<BracedText text="-" braces={PERCENT_BRACES} /> &nbsp;{' '}
<BracedText text="-" braces={PERCENT_BRACES} />
</>
);
}
return (
<>
<BracedText
text={formatPercent(
progress(
compactionStatus.bytesCompacted,
compactionStatus.bytesAwaitingCompaction,
),
)}
braces={PERCENT_BRACES}
/>{' '}
&nbsp;{' '}
<BracedText
text={formatPercent(
progress(
compactionStatus.segmentCountCompacted,
compactionStatus.segmentCountAwaitingCompaction,
),
)}
braces={PERCENT_BRACES}
/>{' '}
&nbsp;{' '}
<BracedText
text={formatPercent(
progress(
compactionStatus.intervalCountCompacted,
compactionStatus.intervalCountAwaitingCompaction,
),
)}
braces={PERCENT_BRACES}
/>
</>
);
},
},
{
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;
if (!compactionStatus) {
return <BracedText text="-" braces={leftToBeCompactedValues} />;
}
return (
<BracedText
text={formatLeftToBeCompacted(compactionStatus.bytesAwaitingCompaction)}
braces={leftToBeCompactedValues}
/>
);
},
},
{
Header: 'Retention',
show: capabilities.hasCoordinatorAccess() && hiddenColumns.exists('Retention'),
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 (
<span
onClick={() =>
this.setState({
retentionDialogOpenOn: {
datasource: row.original.datasource,
rules: row.original.rules,
datasource,
rules,
},
})
}
className="clickable-cell"
>
{text}&nbsp;
{rules.length
? DatasourcesView.formatRules(rules)
: `Cluster default: ${DatasourcesView.formatRules(defaultRules)}`}
&nbsp;
<ActionIcon icon={IconNames.EDIT} />
</span>
);
@ -1070,9 +1208,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,
@ -1101,6 +1238,7 @@ GROUP BY 1`;
{this.renderKillAction()}
{this.renderRetentionDialog()}
{this.renderCompactionDialog()}
{this.renderForceCompactAction()}
</>
);
}

View File

@ -214,7 +214,7 @@ export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputS
public goToPosition(rowColumn: RowColumn) {
const { aceEditor } = this;
if (!aceEditor) return;
aceEditor.focus(); // Grab the focus also
aceEditor.focus(); // Grab the focus
aceEditor.getSelection().moveCursorTo(rowColumn.row, rowColumn.column);
if (rowColumn.endRow && rowColumn.endColumn) {
aceEditor