renderSegmentsTable()

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) }}>&#9632;</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: () => '',
          },
        ]}
      />
    );
  }