in web-console/src/views/supervisors-view/supervisors-view.tsx [713:1021]
private renderSupervisorTable() {
const { filters, onFiltersChange } = this.props;
const { supervisorsState, statsKey, visibleColumns, page, pageSize, sorted } = this.state;
const supervisors = supervisorsState.data || [];
return (
<ReactTable
data={supervisors}
pages={10000000} // Dummy, we are hiding the page selector
loading={supervisorsState.loading}
noDataText={
supervisorsState.isEmpty() ? 'No supervisors' : supervisorsState.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={SMALL_TABLE_PAGE_SIZE_OPTIONS}
showPagination={supervisors.length >= SMALL_TABLE_PAGE_SIZE || page > 0}
showPageJump={false}
ofText=""
columns={[
{
Header: twoLines('Supervisor ID', <i>(datasource)</i>),
id: 'supervisor_id',
accessor: 'supervisor_id',
width: 280,
show: visibleColumns.shown('Supervisor ID'),
Cell: ({ value, original }) => (
<TableClickableCell
tooltip="Show detail"
onClick={() => this.onSupervisorDetail(original)}
hoverIcon={IconNames.SEARCH_TEMPLATE}
>
{value}
</TableClickableCell>
),
},
{
Header: 'Type',
accessor: 'type',
width: 120,
Cell: this.renderSupervisorFilterableCell('type'),
show: visibleColumns.shown('Type'),
},
{
Header: 'Topic/Stream',
accessor: 'source',
width: 200,
Cell: this.renderSupervisorFilterableCell('source'),
show: visibleColumns.shown('Topic/Stream'),
},
{
Header: 'Status',
id: 'detailed_state',
width: 170,
Filter: suggestibleFilterInput([
'CONNECTING_TO_STREAM',
'CREATING_TASKS',
'DISCOVERING_INITIAL_TASKS',
'IDLE',
'INVALID_SPEC',
'LOST_CONTACT_WITH_STREAM',
'PENDING',
'RUNNING',
'SCHEDULER_STOPPED',
'STOPPING',
'SUSPENDED',
'UNABLE_TO_CONNECT_TO_STREAM',
'UNHEALTHY_SUPERVISOR',
'UNHEALTHY_TASKS',
]),
accessor: 'detailed_state',
Cell: ({ value }) => (
<TableFilterableCell
field="detailed_state"
value={value}
filters={filters}
onFiltersChange={onFiltersChange}
>
<span>
<span style={{ color: detailedStateToColor(value) }}>● </span>
{value}
</span>
</TableFilterableCell>
),
show: visibleColumns.shown('Status'),
},
{
Header: 'Configured tasks',
id: 'configured_tasks',
width: 130,
accessor: 'spec',
filterable: false,
sortable: false,
className: 'padded',
Cell: ({ value }) => {
if (!value) return null;
const taskCount = deepGet(value, 'spec.ioConfig.taskCount');
const replicas = deepGet(value, 'spec.ioConfig.replicas');
if (typeof taskCount !== 'number' || typeof replicas !== 'number') return null;
return (
<div>
<div>{formatInteger(taskCount * replicas)}</div>
<div className="detail-line">
{replicas === 1
? '(no replication)'
: `(${pluralIfNeeded(taskCount, 'task')} × ${pluralIfNeeded(
replicas,
'replica',
)})`}
</div>
</div>
);
},
show: visibleColumns.shown('Configured tasks'),
},
{
Header: 'Running tasks',
id: 'running_tasks',
width: 150,
accessor: 'status.payload',
filterable: false,
sortable: false,
Cell: ({ value, original }) => {
if (original.suspended) return;
let label: string | JSX.Element;
const { activeTasks, publishingTasks } = value || {};
if (Array.isArray(activeTasks)) {
label = pluralIfNeeded(activeTasks.length, 'active task');
if (nonEmptyArray(publishingTasks)) {
label = (
<>
<div>{label}</div>
<div>{pluralIfNeeded(publishingTasks.length, 'publishing task')}</div>
</>
);
}
} else {
label = '';
}
return (
<TableClickableCell
tooltip="Go to tasks"
onClick={() => this.goToTasksForSupervisor(original)}
hoverIcon={IconNames.ARROW_TOP_RIGHT}
>
{label}
</TableClickableCell>
);
},
show: visibleColumns.shown('Running tasks'),
},
{
Header: 'Aggregate lag',
accessor: 'status.payload.aggregateLag',
width: 200,
filterable: false,
sortable: false,
className: 'padded',
show: visibleColumns.shown('Aggregate lag'),
Cell: ({ value }) => (isNumberLike(value) ? formatInteger(value) : null),
},
{
Header: twoLines(
'Stats',
<Popover
position={Position.BOTTOM}
content={
<Menu>
{ROW_STATS_KEYS.map(k => (
<MenuItem
key={k}
icon={checkedCircleIcon(k === statsKey)}
text={getRowStatsKeyTitle(k)}
onClick={() => {
this.setState({ statsKey: k });
}}
/>
))}
</Menu>
}
>
<i className="title-button">
{getRowStatsKeyTitle(statsKey)} <Icon icon={IconNames.CARET_DOWN} />
</i>
</Popover>,
),
id: 'stats',
width: 300,
filterable: false,
sortable: false,
className: 'padded',
accessor: 'stats',
Cell: ({ value, original }) => {
if (!value) return;
const activeTasks: SupervisorStatusTask[] | undefined = deepGet(
original,
'status.payload.activeTasks',
);
const activeTaskIds: string[] | undefined = Array.isArray(activeTasks)
? activeTasks.map((t: SupervisorStatusTask) => t.id)
: undefined;
const c = getTotalSupervisorStats(value, statsKey, activeTaskIds);
const seconds = getRowStatsKeySeconds(statsKey);
const issues =
(c.processedWithError || 0) + (c.thrownAway || 0) + (c.unparseable || 0);
const totalLabel = `Total (past ${statsKey}): `;
return issues ? (
<div>
<div
data-tooltip={`${totalLabel}${formatBytes(c.processedBytes * seconds)}`}
>{`Input: ${formatByteRate(c.processedBytes)}`}</div>
{Boolean(c.processed) && (
<div
data-tooltip={`${totalLabel}${formatInteger(c.processed * seconds)} events`}
>{`Processed: ${formatRate(c.processed)}`}</div>
)}
{Boolean(c.processedWithError) && (
<div
className="warning-line"
data-tooltip={`${totalLabel}${formatInteger(
c.processedWithError * seconds,
)} events`}
>
Processed with error: {formatRate(c.processedWithError)}
</div>
)}
{Boolean(c.thrownAway) && (
<div
className="warning-line"
data-tooltip={`${totalLabel}${formatInteger(c.thrownAway * seconds)} events`}
>
Thrown away: {formatRate(c.thrownAway)}
</div>
)}
{Boolean(c.unparseable) && (
<div
className="warning-line"
data-tooltip={`${totalLabel}${formatInteger(c.unparseable * seconds)} events`}
>
Unparseable: {formatRate(c.unparseable)}
</div>
)}
</div>
) : c.processedBytes ? (
<div
data-tooltip={`${totalLabel}${formatInteger(
c.processed * seconds,
)} events, ${formatBytes(c.processedBytes * seconds)}`}
>{`Processed: ${formatRate(c.processed)} (${formatByteRate(
c.processedBytes,
)})`}</div>
) : (
<div>No activity</div>
);
},
show: visibleColumns.shown('Stats'),
},
{
Header: 'Recent errors',
accessor: 'status.payload.recentErrors',
width: 150,
filterable: false,
sortable: false,
show: visibleColumns.shown('Recent errors'),
Cell: ({ value, original }) => {
if (!value) return null;
return (
<TableClickableCell
tooltip="Show errors"
onClick={() => this.onSupervisorDetail(original)}
hoverIcon={IconNames.SEARCH_TEMPLATE}
>
{pluralIfNeeded(value.length, 'error')}
</TableClickableCell>
);
},
},
{
Header: ACTION_COLUMN_LABEL,
id: ACTION_COLUMN_ID,
accessor: 'supervisor_id',
width: ACTION_COLUMN_WIDTH,
filterable: false,
sortable: false,
Cell: ({ value: id, original }) => {
const supervisorActions = this.getSupervisorActions(original);
return (
<ActionCell
onDetail={() => this.onSupervisorDetail(original)}
actions={supervisorActions}
menuTitle={id}
/>
);
},
},
]}
/>
);
}