in web-console/src/views/datasources-view/datasources-view.tsx [1166:1709]
private renderDatasourcesTable() {
const { goToTasks, capabilities, filters, onFiltersChange } = this.props;
const { datasourcesAndDefaultRulesState, showUnused, visibleColumns, showSegmentTimeline } =
this.state;
let { datasources, defaultRules } = datasourcesAndDefaultRulesState.data || { datasources: [] };
if (!showUnused) {
datasources = datasources.filter(d => !d.unused);
}
// Calculate column values for bracing
const totalDataSizeValues = datasources.map(d => formatTotalDataSize(d.total_data_size));
const minSegmentRowsValues = datasources.map(d => formatSegmentRows(d.min_segment_rows));
const avgSegmentRowsValues = datasources.map(d => formatSegmentRows(d.avg_segment_rows));
const maxSegmentRowsValues = datasources.map(d => formatSegmentRows(d.max_segment_rows));
const minSegmentSizeValues = datasources.map(d => formatSegmentSize(d.min_segment_size));
const avgSegmentSizeValues = datasources.map(d => formatSegmentSize(d.avg_segment_size));
const maxSegmentSizeValues = datasources.map(d => formatSegmentSize(d.max_segment_size));
const totalRowsValues = datasources.map(d => formatTotalRows(d.total_rows));
const avgRowSizeValues = datasources.map(d => formatAvgRowSize(d.avg_row_size));
const replicatedSizeValues = datasources.map(d => formatReplicatedSize(d.replicated_size));
const leftToBeCompactedValues = datasources.map(d =>
d.compaction?.status
? formatLeftToBeCompacted(d.compaction?.status.bytesAwaitingCompaction)
: '-',
);
return (
<ReactTable
data={datasources}
loading={datasourcesAndDefaultRulesState.loading}
noDataText={
datasourcesAndDefaultRulesState.getErrorMessage() ||
(!datasourcesAndDefaultRulesState.loading && datasources && !datasources.length
? 'No datasources'
: '')
}
filterable
filtered={filters}
onFilteredChange={onFiltersChange}
defaultPageSize={STANDARD_TABLE_PAGE_SIZE}
pageSizeOptions={STANDARD_TABLE_PAGE_SIZE_OPTIONS}
showPagination={datasources.length > STANDARD_TABLE_PAGE_SIZE}
columns={[
{
Header: twoLines('Datasource', 'name'),
show: visibleColumns.shown('Datasource name'),
accessor: 'datasource',
width: 150,
Cell: ({ value, original }) => (
<TableClickableCell
onClick={() => this.onDetail(original)}
hoverIcon={IconNames.SEARCH_TEMPLATE}
tooltip="Show detail"
>
{showSegmentTimeline ? (
<>
<span style={{ color: getDatasourceColor(value) }}>■</span> {value}
</>
) : (
value
)}
</TableClickableCell>
),
},
{
Header: 'Availability',
show: visibleColumns.shown('Availability'),
filterable: false,
width: 220,
accessor: 'num_segments',
className: 'padded',
Cell: ({ value: num_segments, original }) => {
const { datasource, unused, num_segments_to_load, num_zero_replica_segments, rules } =
original as Datasource;
if (unused) {
return (
<span>
<span style={{ color: DatasourcesView.UNUSED_COLOR }}>● </span>
Unused
</span>
);
}
const hasZeroReplicationRule = RuleUtil.hasZeroReplicaRule(rules, defaultRules);
const descriptor = hasZeroReplicationRule ? 'pre-cached' : 'available';
const segmentsEl = (
<a
onClick={() =>
this.setState({ showSegmentTimeline: { capabilities, datasource } })
}
data-tooltip="Show in segment timeline"
>
{pluralIfNeeded(num_segments, 'segment')}
</a>
);
const percentZeroReplica = (
Math.floor((num_zero_replica_segments / num_segments) * 1000) / 10
).toFixed(1);
if (typeof num_segments_to_load !== 'number' || typeof num_segments !== 'number') {
return '-';
} else if (num_segments === 0) {
return (
<span>
<span style={{ color: DatasourcesView.EMPTY_COLOR }}>● </span>
Empty
</span>
);
} else if (num_segments_to_load === 0) {
return (
<span>
<span style={{ color: DatasourcesView.FULLY_AVAILABLE_COLOR }}>
●
</span>
{assemble(
num_segments !== num_zero_replica_segments
? `Fully ${descriptor}`
: undefined,
hasZeroReplicationRule ? `${percentZeroReplica}% deep storage only` : '',
).join(', ')}{' '}
({segmentsEl})
</span>
);
} else {
const numAvailableSegments = num_segments - num_segments_to_load;
const percentAvailable = (
Math.floor((numAvailableSegments / num_segments) * 1000) / 10
).toFixed(1);
return (
<span>
<span style={{ color: DatasourcesView.PARTIALLY_AVAILABLE_COLOR }}>
{numAvailableSegments ? '\u25cf' : '\u25cb'}
</span>
{`${percentAvailable}% ${descriptor}${
hasZeroReplicationRule ? `, ${percentZeroReplica}% deep storage only` : ''
}`}{' '}
({segmentsEl})
</span>
);
}
},
sortMethod: (d1, d2) => {
const percentAvailable1 = d1.num_available / d1.num_total;
const percentAvailable2 = d2.num_available / d2.num_total;
return percentAvailable1 - percentAvailable2 || d1.num_total - d2.num_total;
},
},
{
Header: twoLines('Historical', 'load/drop queues'),
show: visibleColumns.shown('Historical load/drop queues'),
accessor: 'num_segments_to_load',
filterable: false,
width: 180,
className: 'padded',
Cell: ({ original }) => {
const { num_segments_to_load, num_segments_to_drop } = original as Datasource;
return formatLoadDrop(num_segments_to_load, num_segments_to_drop);
},
},
{
Header: twoLines('Total', 'data size'),
show: visibleColumns.shown('Total data size'),
accessor: 'total_data_size',
filterable: false,
width: 100,
className: 'padded',
Cell: ({ value }) => (
<BracedText text={formatTotalDataSize(value)} braces={totalDataSizeValues} />
),
},
{
Header: 'Running tasks',
show: visibleColumns.shown('Running tasks'),
id: 'running_tasks',
accessor: d => countRunningTasks(d.runningTasks),
filterable: false,
width: 200,
Cell: ({ original }) => {
const { runningTasks } = original;
if (!runningTasks) return;
return (
<TableClickableCell
onClick={() => goToTasks(original.datasource)}
hoverIcon={IconNames.ARROW_TOP_RIGHT}
tooltip="Go to tasks"
>
{formatRunningTasks(runningTasks)}
</TableClickableCell>
);
},
},
{
Header: twoLines('Segment rows', 'minimum / average / maximum'),
show: capabilities.hasSql() && visibleColumns.shown('Segment rows'),
accessor: 'avg_segment_rows',
filterable: false,
width: 230,
className: 'padded',
Cell: ({ value, original }) => {
const { min_segment_rows, max_segment_rows } = original as Datasource;
if (
isNumberLikeNaN(value) ||
isNumberLikeNaN(min_segment_rows) ||
isNumberLikeNaN(max_segment_rows)
)
return '-';
return (
<>
<BracedText
text={formatSegmentRows(min_segment_rows)}
braces={minSegmentRowsValues}
/>{' '}
{' '}
<BracedText text={formatSegmentRows(value)} braces={avgSegmentRowsValues} />{' '}
{' '}
<BracedText
text={formatSegmentRows(max_segment_rows)}
braces={maxSegmentRowsValues}
/>
</>
);
},
},
{
Header: twoLines('Segment size', 'minimum / average / maximum'),
show: capabilities.hasSql() && visibleColumns.shown('Segment size'),
accessor: 'avg_segment_size',
filterable: false,
width: 270,
className: 'padded',
Cell: ({ value, original }) => {
const { min_segment_size, max_segment_size } = original as Datasource;
if (
isNumberLikeNaN(value) ||
isNumberLikeNaN(min_segment_size) ||
isNumberLikeNaN(max_segment_size)
)
return '-';
return (
<>
<BracedText
text={formatSegmentSize(min_segment_size)}
braces={minSegmentSizeValues}
/>{' '}
{' '}
<BracedText text={formatSegmentSize(value)} braces={avgSegmentSizeValues} />{' '}
{' '}
<BracedText
text={formatSegmentSize(max_segment_size)}
braces={maxSegmentSizeValues}
/>
</>
);
},
},
{
Header: twoLines('Segment', 'granularity'),
show: capabilities.hasSql() && visibleColumns.shown('Segment granularity'),
id: 'segment_granularity',
accessor: segmentGranularityCountsToRank,
filterable: false,
width: 100,
className: 'padded',
Cell: ({ original }) => {
const {
num_segments,
minute_aligned_segments,
hour_aligned_segments,
day_aligned_segments,
month_aligned_segments,
year_aligned_segments,
all_granularity_segments,
} = original as Datasource;
const segmentGranularities: string[] = [];
if (!num_segments || isNumberLikeNaN(year_aligned_segments)) return '-';
if (all_granularity_segments) {
segmentGranularities.push('All');
}
if (year_aligned_segments) {
segmentGranularities.push('Year');
}
if (month_aligned_segments !== year_aligned_segments) {
segmentGranularities.push('Month');
}
if (day_aligned_segments !== month_aligned_segments) {
segmentGranularities.push('Day');
}
if (hour_aligned_segments !== day_aligned_segments) {
segmentGranularities.push('Hour');
}
if (minute_aligned_segments !== hour_aligned_segments) {
segmentGranularities.push('Minute');
}
if (
Number(num_segments) - Number(all_granularity_segments) !==
Number(minute_aligned_segments)
) {
segmentGranularities.push('Sub minute');
}
return segmentGranularities.join(', ');
},
},
{
Header: twoLines('Total', 'rows'),
show: capabilities.hasSql() && visibleColumns.shown('Total rows'),
accessor: 'total_rows',
filterable: false,
width: 110,
className: 'padded',
Cell: ({ value }) => {
if (isNumberLikeNaN(value)) return '-';
return (
<BracedText
text={formatTotalRows(value)}
braces={totalRowsValues}
unselectableThousandsSeparator
/>
);
},
},
{
Header: twoLines('Avg. row size', '(bytes)'),
show: capabilities.hasSql() && visibleColumns.shown('Avg. row size'),
accessor: 'avg_row_size',
filterable: false,
width: 100,
className: 'padded',
Cell: ({ value }) => {
if (isNumberLikeNaN(value)) return '-';
return (
<BracedText
text={formatAvgRowSize(value)}
braces={avgRowSizeValues}
unselectableThousandsSeparator
/>
);
},
},
{
Header: twoLines('Replicated', 'size'),
show: capabilities.hasSql() && visibleColumns.shown('Replicated size'),
accessor: 'replicated_size',
filterable: false,
width: 100,
className: 'padded',
Cell: ({ value }) => {
if (isNumberLikeNaN(value)) return '-';
return (
<BracedText text={formatReplicatedSize(value)} braces={replicatedSizeValues} />
);
},
},
{
Header: 'Compaction',
show: capabilities.hasCoordinatorAccess() && visibleColumns.shown('Compaction'),
id: 'compactionStatus',
accessor: row => Boolean(row.compaction?.status),
filterable: false,
width: 180,
Cell: ({ original }) => {
const { datasource, compaction } = original as Datasource;
if (!compaction) return;
return (
<TableClickableCell
tooltip="Open compaction configuration"
disabled={!compaction}
onClick={() => {
if (!compaction) return;
this.setState({
compactionDialogOpenOn: {
datasource,
compactionConfig: compaction.config,
},
});
}}
hoverIcon={IconNames.EDIT}
>
{formatCompactionInfo(compaction)}
</TableClickableCell>
);
},
},
{
Header: twoLines('% Compacted', 'bytes / segments / intervals'),
show: capabilities.hasCoordinatorAccess() && visibleColumns.shown('% Compacted'),
id: 'percentCompacted',
width: 200,
accessor: ({ compaction }) => {
const status = compaction?.status;
return status?.bytesCompacted
? status.bytesCompacted / (status.bytesAwaitingCompaction + status.bytesCompacted)
: 0;
},
filterable: false,
className: 'padded',
Cell: ({ original }) => {
const { compaction } = original as Datasource;
if (!compaction) return;
const { status } = compaction;
if (!status || zeroCompactionStatus(status)) {
return (
<>
<BracedText text="-" braces={PERCENT_BRACES} /> {' '}
<BracedText text="-" braces={PERCENT_BRACES} /> {' '}
<BracedText text="-" braces={PERCENT_BRACES} />
</>
);
}
return (
<>
<BracedText
text={formatPercent(
progress(status.bytesCompacted, status.bytesAwaitingCompaction),
)}
braces={PERCENT_BRACES}
/>{' '}
{' '}
<BracedText
text={formatPercent(
progress(status.segmentCountCompacted, status.segmentCountAwaitingCompaction),
)}
braces={PERCENT_BRACES}
/>{' '}
{' '}
<BracedText
text={formatPercent(
progress(
status.intervalCountCompacted,
status.intervalCountAwaitingCompaction,
),
)}
braces={PERCENT_BRACES}
/>
</>
);
},
},
{
Header: twoLines('Left to be', 'compacted'),
show:
capabilities.hasCoordinatorAccess() && visibleColumns.shown('Left to be compacted'),
id: 'leftToBeCompacted',
width: 100,
accessor: ({ compaction }) => {
const status = compaction?.status;
return status?.bytesAwaitingCompaction || 0;
},
filterable: false,
className: 'padded',
Cell: ({ original }) => {
const { compaction } = original as Datasource;
if (!compaction) return;
const { status } = compaction;
if (!status) {
return <BracedText text="-" braces={leftToBeCompactedValues} />;
}
return (
<BracedText
text={formatLeftToBeCompacted(status.bytesAwaitingCompaction)}
braces={leftToBeCompactedValues}
/>
);
},
},
{
Header: 'Retention',
show: capabilities.hasCoordinatorAccess() && visibleColumns.shown('Retention'),
id: 'retention',
accessor: row => row.rules?.length || 0,
filterable: false,
width: 200,
Cell: ({ original }) => {
const { datasource, rules } = original as Datasource;
if (!rules) return;
return (
<TableClickableCell
tooltip="Open retention (load rule) configuration"
disabled={!defaultRules}
onClick={() => {
if (!defaultRules) return;
this.setState({
retentionDialogOpenOn: {
datasource,
rules,
defaultRules,
},
});
}}
hoverIcon={IconNames.EDIT}
>
{rules.length
? DatasourcesView.formatRules(rules)
: defaultRules
? `Cluster default: ${DatasourcesView.formatRules(defaultRules)}`
: ''}
</TableClickableCell>
);
},
},
{
Header: ACTION_COLUMN_LABEL,
accessor: 'datasource',
id: ACTION_COLUMN_ID,
width: ACTION_COLUMN_WIDTH,
filterable: false,
sortable: false,
Cell: ({ value: datasource, original }) => {
const { unused, rules, compaction } = original as Datasource;
const datasourceActions = this.getDatasourceActions(
datasource,
unused,
rules,
compaction,
);
return (
<ActionCell
onDetail={() => {
this.onDetail(original);
}}
disableDetail={unused}
actions={datasourceActions}
menuTitle={datasource}
/>
);
},
},
]}
/>
);
}