in web-console/src/views/segments-view/segments-view.tsx [541:958]
renderSegmentsTable() {
const { capabilities, filters, onFiltersChange } = this.props;
const {
segmentsState,
visibleColumns,
groupByInterval,
page,
pageSize,
sorted,
showSegmentTimeline,
} = this.state;
const segments = segmentsState.data || [];
const sizeValues = segments.map(d => formatBytes(d.size)).concat('(realtime)');
const numRowsValues = segments.map(d => formatInteger(d.num_rows)).concat('(unknown)');
const avgRowSizeValues = segments.map(d => formatInteger(d.avg_row_size));
const hasSql = capabilities.hasSql();
// Only allow filtering of columns other than datasource if in SQL mode, or if we are filtering on an exact datasource
const allowGeneralFilter =
hasSql ||
filters.some(
filter => filter.id === 'datasource' && parseFilterModeAndNeedle(filter)?.mode === '=',
);
return (
<ReactTable
data={segments}
pages={10000000} // Dummy, we are hiding the page selector
loading={segmentsState.loading}
noDataText={
segmentsState.isEmpty()
? `No segments${filters.length ? ' matching filter' : ''}`
: segmentsState.getErrorMessage() || ''
}
manual
filterable
filtered={filters}
onFilteredChange={onFiltersChange}
sorted={sorted}
onSortedChange={sorted => this.setState({ sorted })}
page={page}
onPageChange={page => this.setState({ page })}
pageSize={pageSize}
onPageSizeChange={pageSize => this.setState({ pageSize })}
pageSizeOptions={STANDARD_TABLE_PAGE_SIZE_OPTIONS}
showPagination={segments.length >= STANDARD_TABLE_PAGE_SIZE || page > 0}
showPageJump={false}
ofText=""
pivotBy={groupByInterval ? ['interval'] : []}
columns={[
{
Header: 'Segment ID',
show: visibleColumns.shown('Segment ID'),
accessor: 'segment_id',
width: 280,
filterable: allowGeneralFilter,
Cell: row => (
<TableClickableCell
tooltip="Show detail"
onClick={() => this.onDetail(row.value, row.row.datasource)}
hoverIcon={IconNames.SEARCH_TEMPLATE}
>
{row.value}
</TableClickableCell>
),
},
{
Header: 'Datasource',
show: visibleColumns.shown('Datasource'),
accessor: 'datasource',
width: 140,
Cell: this.renderFilterableCell(
'datasource',
false,
showSegmentTimeline
? value => (
<>
<span style={{ color: getDatasourceColor(value) }}>■</span> {value}
</>
)
: String,
),
},
{
Header: 'Interval',
show: groupByInterval,
accessor: 'interval',
width: 120,
defaultSortDesc: true,
filterable: allowGeneralFilter,
Cell: this.renderFilterableCell('interval'),
},
{
Header: 'Start',
show: visibleColumns.shown('Start'),
accessor: 'start',
headerClassName: 'enable-comparisons',
width: 180,
defaultSortDesc: true,
filterable: allowGeneralFilter,
Cell: this.renderFilterableCell('start', true),
},
{
Header: 'End',
show: visibleColumns.shown('End'),
accessor: 'end',
headerClassName: 'enable-comparisons',
width: 180,
defaultSortDesc: true,
filterable: allowGeneralFilter,
Cell: this.renderFilterableCell('end', true),
},
{
Header: 'Version',
show: visibleColumns.shown('Version'),
accessor: 'version',
width: 180,
defaultSortDesc: true,
filterable: allowGeneralFilter,
Cell: this.renderFilterableCell('version', true),
},
{
Header: 'Time span',
show: visibleColumns.shown('Time span'),
id: 'time_span',
className: 'padded',
accessor: ({ start, end }) => computeSegmentTimeSpan(start, end),
width: 100,
sortable: false,
filterable: false,
},
{
Header: 'Shard type',
show: visibleColumns.shown('Shard type'),
id: 'shard_type',
width: 100,
sortable: false,
accessor: ({ shard_spec }) => {
if (typeof shard_spec?.type !== 'string') return '-';
return shard_spec?.type;
},
Cell: this.renderFilterableCell('shard_type', true),
},
{
Header: 'Shard spec',
show: visibleColumns.shown('Shard spec'),
id: 'shard_spec',
accessor: 'shard_spec',
width: 400,
sortable: false,
filterable: false,
Cell: ({ value }) => {
const onShowFullShardSpec = () => {
this.setState({
showFullShardSpec:
value && typeof value === 'object'
? JSONBig.stringify(value, undefined, 2)
: String(value),
});
};
switch (value?.type) {
case 'range': {
const dimensions: string[] = value.dimensions || [];
const formatEdge = (values: string[]) =>
dimensions.map((d, i) => formatRangeDimensionValue(d, values[i])).join('; ');
return (
<TableClickableCell
className="range-detail"
tooltip="Show full shardSpec"
onClick={onShowFullShardSpec}
hoverIcon={IconNames.EYE_OPEN}
>
<span className="range-label">Start:</span>
{Array.isArray(value.start) ? formatEdge(value.start) : '-∞'}
<br />
<span className="range-label">End:</span>
{Array.isArray(value.end) ? formatEdge(value.end) : '∞'}
</TableClickableCell>
);
}
case 'single': {
return (
<TableClickableCell
className="range-detail"
tooltip="Show full shardSpec"
onClick={onShowFullShardSpec}
hoverIcon={IconNames.EYE_OPEN}
>
<span className="range-label">Start:</span>
{value.start != null
? formatRangeDimensionValue(value.dimension, value.start)
: '-∞'}
<br />
<span className="range-label">End:</span>
{value.end != null
? formatRangeDimensionValue(value.dimension, value.end)
: '∞'}
</TableClickableCell>
);
}
case 'hashed': {
const { partitionDimensions } = value;
if (!Array.isArray(partitionDimensions)) return JSONBig.stringify(value);
return (
<TableClickableCell
tooltip="Show full shardSpec"
onClick={onShowFullShardSpec}
hoverIcon={IconNames.EYE_OPEN}
>
{`hash(${
partitionDimensions.length
? partitionDimensions.join(', ')
: '<all dimensions>'
})`}
</TableClickableCell>
);
}
case 'numbered':
case 'none':
case 'tombstone':
return (
<TableClickableCell
tooltip="Show full shardSpec"
onClick={onShowFullShardSpec}
hoverIcon={IconNames.EYE_OPEN}
>
No detail
</TableClickableCell>
);
default:
return (
<TableClickableCell
tooltip="Show full shardSpec"
onClick={onShowFullShardSpec}
hoverIcon={IconNames.EYE_OPEN}
>
{JSONBig.stringify(value)}
</TableClickableCell>
);
}
},
Aggregated: opt => {
const { subRows } = opt;
const previewValues = filterMap(subRows, row => row['shard_spec'].type);
const previewCount = countBy(previewValues);
return (
<div className="default-aggregated">
{Object.keys(previewCount)
.sort()
.map(v => `${v} (${previewCount[v]})`)
.join(', ')}
</div>
);
},
},
{
Header: 'Partition',
show: visibleColumns.shown('Partition'),
accessor: 'partition_num',
width: 60,
filterable: false,
className: 'padded',
},
{
Header: 'Size',
show: visibleColumns.shown('Size'),
accessor: 'size',
filterable: false,
defaultSortDesc: true,
width: 120,
className: 'padded',
Cell: row => (
<BracedText
text={
row.value === 0 && row.original.is_realtime === 1
? '(realtime)'
: formatBytes(row.value)
}
braces={sizeValues}
/>
),
},
{
Header: 'Num rows',
show: hasSql && visibleColumns.shown('Num rows'),
accessor: 'num_rows',
filterable: false,
defaultSortDesc: true,
width: 120,
className: 'padded',
Cell: row => (
<BracedText
text={row.original.is_available ? formatInteger(row.value) : '(unknown)'}
braces={numRowsValues}
unselectableThousandsSeparator
/>
),
},
{
Header: twoLines('Avg. row size', <i>(bytes)</i>),
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={formatInteger(value)}
braces={avgRowSizeValues}
unselectableThousandsSeparator
/>
);
},
},
{
Header: twoLines('Replicas', <i>(actual)</i>),
show: hasSql && visibleColumns.shown('Replicas'),
accessor: 'num_replicas',
width: 80,
filterable: false,
defaultSortDesc: true,
className: 'padded',
},
{
Header: twoLines('Replication factor', <i>(desired)</i>),
show: visibleColumns.shown('Replication factor'),
accessor: 'replication_factor',
width: 80,
filterable: false,
defaultSortDesc: true,
className: 'padded',
},
{
Header: 'Is available',
show: hasSql && visibleColumns.shown('Is available'),
id: 'is_available',
accessor: row => String(Boolean(row.is_available)),
Filter: BooleanFilterInput,
className: 'padded',
width: 100,
},
{
Header: 'Is active',
show: hasSql && visibleColumns.shown('Is active'),
id: 'is_active',
accessor: row => String(Boolean(row.is_active)),
Filter: BooleanFilterInput,
className: 'padded',
width: 100,
},
{
Header: 'Is realtime',
show: visibleColumns.shown('Is realtime'),
id: 'is_realtime',
accessor: row => String(Boolean(row.is_realtime)),
Filter: BooleanFilterInput,
className: 'padded',
width: 100,
},
{
Header: 'Is published',
show: hasSql && visibleColumns.shown('Is published'),
id: 'is_published',
accessor: row => String(Boolean(row.is_published)),
Filter: BooleanFilterInput,
className: 'padded',
width: 100,
},
{
Header: 'Is overshadowed',
show: visibleColumns.shown('Is overshadowed'),
id: 'is_overshadowed',
accessor: row => String(Boolean(row.is_overshadowed)),
Filter: BooleanFilterInput,
className: 'padded',
width: 100,
},
{
Header: ACTION_COLUMN_LABEL,
show: capabilities.hasCoordinatorAccess(),
id: ACTION_COLUMN_ID,
accessor: 'segment_id',
width: ACTION_COLUMN_WIDTH,
filterable: false,
sortable: false,
Cell: row => {
if (row.aggregated) return '';
const id = row.value;
const datasource = row.row.datasource;
return (
<ActionCell
onDetail={() => {
this.onDetail(id, datasource);
}}
actions={this.getSegmentActions(id, datasource)}
menuTitle={id}
/>
);
},
Aggregated: () => '',
},
]}
/>
);
}