• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MarkUsProject / Markus / 30502663235

30 Jul 2026 12:26AM UTC coverage: 90.503%. Remained the same
30502663235

Pull #8089

github

web-flow
Merge 53229325b into 92f5116fa
Pull Request #8089: feat: added row number along side of grades table

1226 of 2491 branches covered (49.22%)

Branch coverage included in aggregate %.

5 of 6 new or added lines in 1 file covered. (83.33%)

1 existing line in 1 file now uncovered.

47735 of 51608 relevant lines covered (92.5%)

129.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.75
/app/javascript/Components/table/table.jsx
1
import React from "react";
2
import {Grid} from "react-loader-spinner";
3
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
4

5
import {
6
  createColumnHelper,
7
  getCoreRowModel,
8
  getExpandedRowModel,
9
  getFacetedRowModel,
10
  getFacetedUniqueValues,
11
  getFilteredRowModel,
12
  getGroupedRowModel,
13
  getSortedRowModel,
14
  useReactTable,
15
} from "@tanstack/react-table";
16
import FilterCell from "./filter";
17
import TableHeaderCell from "./table_header_cell";
18
import TableRow from "./table_row";
19

20
export const defaultNoDataText = () => I18n.t("table.no_data");
61✔
21

22
const columnHelper = createColumnHelper();
12✔
23
export const SELECTION_COLUMN_ID = "select";
12✔
24
export const ROW_NUMBER_COLUMN_ID = "row_number";
12✔
25
const FILTER_VARIANT_SELECT = "select";
12✔
26

27
export const rowNumberColumn = columnHelper.display({
12✔
28
  id: ROW_NUMBER_COLUMN_ID,
29
  header: () => (
30
    <span aria-label={I18n.t("table.row_number")} title={I18n.t("table.row_number")}>
187✔
31
      #
32
    </span>
33
  ),
34
  size: 42,
35
  minSize: 42,
36
  maxSize: 42,
37
  enableResizing: false,
38
  enableSorting: false,
39
  enableHiding: false,
40
  meta: {
41
    className: "rt-row-number",
42
    headerClassName: "rt-row-number",
43
  },
NEW
UNCOV
44
  cell: () => null,
×
45
});
46

47
export const expanderColumn = columnHelper.display({
12✔
48
  id: "expander",
49
  header: () => null,
2✔
50
  size: 30,
51
  maxSize: 30,
52
  cell: ({row}) => {
53
    const icon = row.getIsExpanded() ? "fa-chevron-up" : "fa-chevron-down";
6!
54
    const title = row.getIsExpanded() ? I18n.t("table.hide_details") : I18n.t("table.show_details");
6!
55
    return row.getCanExpand() ? (
6!
56
      <div
57
        className={`rt-expandable ${row.getIsExpanded() ? "-open" : ""}`}
6!
58
        onClick={row.getToggleExpandedHandler()}
59
        data-testid="expander-button"
60
      >
61
        <FontAwesomeIcon icon={icon} title={title} />
62
      </div>
63
    ) : null;
64
  },
65
});
66

67
export const selectionColumn = columnHelper.display({
12✔
68
  id: SELECTION_COLUMN_ID,
69
  header: ({table}) => {
70
    const checkboxRef = React.useRef(null);
94✔
71

72
    React.useEffect(() => {
94✔
73
      if (checkboxRef.current) {
74!
74
        checkboxRef.current.indeterminate =
74✔
75
          table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected();
74!
76
      }
77
    }, [table.getIsSomeRowsSelected(), table.getIsAllRowsSelected()]);
78

79
    return (
94✔
80
      <input
81
        ref={checkboxRef}
82
        type="checkbox"
83
        checked={table.getIsAllRowsSelected()}
84
        onChange={table.getToggleAllRowsSelectedHandler()}
85
      />
86
    );
87
  },
88
  size: 30,
89
  maxSize: 30,
90
  enableResizing: false,
91
  enableSorting: false,
92
  cell: ({row}) => (
93
    <input
128✔
94
      type="checkbox"
95
      checked={row.getIsSelected()}
96
      disabled={!row.getCanSelect()}
97
      onChange={row.getToggleSelectedHandler()}
98
    />
99
  ),
100
});
101

102
export default function Table({
103
  columns,
104
  data,
105
  noDataText,
106
  initialState,
107
  loading,
108
  renderSubComponent,
109
  renderSubRows,
110
  getRowCanExpand,
111
  getRowId,
112
  enableRowSelection,
113
  rowSelection: externalRowSelection,
114
  columnFilters: externalColumnFilters,
115
  onColumnFiltersChange: externalOnColumnFiltersChange,
116
  onRowSelectionChange,
117
}) {
118
  const [internalColumnFilters, setInternalColumnFilters] = React.useState([]);
472✔
119
  const [columnSizing, setColumnSizing] = React.useState({});
472✔
120
  const [columnVisibility, setColumnVisibility] = React.useState({
472✔
121
    inactive: false,
122
    ...initialState?.columnVisibility,
123
  });
124
  const [expanded, setExpanded] = React.useState({});
472✔
125
  const [internalRowSelection, setInternalRowSelection] = React.useState({});
472✔
126
  const [grouping, setGrouping] = React.useState(initialState?.grouping ?? []);
472✔
127

128
  const columnFilters = React.useMemo(
472✔
129
    () => (externalColumnFilters !== undefined ? externalColumnFilters : internalColumnFilters),
221✔
130
    [externalColumnFilters, internalColumnFilters]
131
  );
132

133
  const handleColumnFiltersChange = React.useMemo(
472✔
134
    () =>
135
      externalOnColumnFiltersChange !== undefined
284✔
136
        ? externalOnColumnFiltersChange
137
        : setInternalColumnFilters,
138
    [externalOnColumnFiltersChange]
139
  );
140

141
  const rowSelection = React.useMemo(
472✔
142
    () => (externalRowSelection !== undefined ? externalRowSelection : internalRowSelection),
200✔
143
    [externalRowSelection, internalRowSelection]
144
  );
145

146
  const handleRowSelectionChange = React.useMemo(
472✔
147
    () => (onRowSelectionChange !== undefined ? onRowSelectionChange : setInternalRowSelection),
249✔
148
    [onRowSelectionChange]
149
  );
150

151
  const finalColumns = React.useMemo(() => {
472✔
152
    let cols = [...columns];
187✔
153
    if (enableRowSelection) {
187✔
154
      cols = [selectionColumn, ...cols];
91✔
155
    }
156
    if (renderSubComponent || renderSubRows) {
187✔
157
      cols = [expanderColumn, ...cols];
2✔
158
    }
159
    return [rowNumberColumn, ...cols];
187✔
160
  }, [columns, enableRowSelection, renderSubComponent, renderSubRows]);
161

162
  const table = useReactTable({
472✔
163
    data,
164
    columns: finalColumns,
165
    state: {
166
      columnFilters,
167
      columnSizing,
168
      columnVisibility,
169
      expanded,
170
      rowSelection,
171
      grouping,
172
    },
173
    initialState: initialState,
174
    onColumnFiltersChange: handleColumnFiltersChange,
175
    onColumnSizingChange: setColumnSizing,
176
    onColumnVisibilityChange: setColumnVisibility,
177
    onExpandedChange: setExpanded,
178
    onGroupingChange: setGrouping,
179
    onRowSelectionChange: handleRowSelectionChange,
180
    getCoreRowModel: getCoreRowModel(),
181
    getExpandedRowModel: getExpandedRowModel(),
182
    getFilteredRowModel: getFilteredRowModel(),
183
    getGroupedRowModel: getGroupedRowModel(),
184
    getSortedRowModel: getSortedRowModel(),
185
    getFacetedUniqueValues: getFacetedUniqueValues(),
186
    getFacetedRowModel: getFacetedRowModel(),
187
    getRowCanExpand,
188
    getRowId,
189
    groupedColumnMode: false,
190
    enableSortingRemoval: false,
191
    enableColumnResizing: true,
192
    enableRowSelection: enableRowSelection,
193
    columnResizeMode: "onChange",
194
  });
195

196
  const centerTotalSize = table.getCenterTotalSize();
472✔
197
  const rows = table.getRowModel().rows;
472✔
198

199
  const tableHeaders = (
200
    <div className="rt-thead -header" style={{minWidth: centerTotalSize}}>
472✔
201
      {table.getHeaderGroups().map(headerGroup => (
202
        <div className="rt-tr" role="row" key={headerGroup.id}>
472✔
203
          {headerGroup.headers.map(header => (
204
            <TableHeaderCell
2,698✔
205
              key={header.id}
206
              header={header}
207
              size={header.getSize()}
208
              isSorted={header.column.getIsSorted()}
209
              isResizing={header.column.getIsResizing()}
210
              allRowsSelected={
211
                header.column.id === SELECTION_COLUMN_ID ? table.getIsAllRowsSelected() : undefined
2,698✔
212
              }
213
            />
214
          ))}
215
        </div>
216
      ))}
217
    </div>
218
  );
219

220
  const showFilters = React.useMemo(
472✔
221
    () => table.getAllColumns().some(column => column.getCanFilter()),
469✔
222
    [table, finalColumns]
223
  );
224
  const tableFilters = showFilters && (
472✔
225
    <div className="rt-thead -filters" style={{minWidth: centerTotalSize}}>
226
      {table.getHeaderGroups().map(headerGroup => (
227
        <div className="rt-tr" role="row" key={headerGroup.id}>
457✔
228
          {headerGroup.headers.map(header => (
229
            <FilterCell
2,663✔
230
              key={header.id}
231
              size={header.getSize()}
232
              column={header.column}
233
              filterValue={header.column.getFilterValue()}
234
              facetedUniqueValues={
235
                header.column.columnDef.meta?.filterVariant === FILTER_VARIANT_SELECT
2,663✔
236
                  ? header.column.getFacetedUniqueValues()
237
                  : null
238
              }
239
            />
240
          ))}
241
        </div>
242
      ))}
243
    </div>
244
  );
245

246
  return (
472✔
247
    <div className="Table -highlight" style={{maxHeight: "500px"}}>
248
      <div className="rt-table" role="grid">
249
        {tableHeaders}
250
        {tableFilters}
251
        <div className="rt-tbody" style={{minWidth: centerTotalSize}}>
252
          {rows.map((row, index) => (
253
            <TableRow
611✔
254
              row={row}
255
              rowNumber={index + 1}
256
              isExpanded={row.getIsExpanded()}
257
              isGrouped={row.getIsGrouped()}
258
              isSelected={row.getIsSelected()}
259
              key={row.id}
260
              renderSubComponent={renderSubComponent}
261
              // columnSizing is not used directly in TableRow, but is passed to trigger
262
              // re-render when column sizes change
263
              columnSizing={columnSizing}
264
              columns={finalColumns}
265
            />
266
          ))}
267
          {loading && rows.length > 0 && (
593!
268
            <div
269
              className="loading-spinner"
270
              style={{
271
                position: "absolute",
272
                bottom: 0,
273
                left: 0,
274
                right: 0,
275
                height: "auto",
276
                backgroundColor: "#fff",
277
                zIndex: 10,
278
                border: "solid 1px rgba(0, 0, 0, 0.05)",
279
              }}
280
            >
281
              <Grid
282
                visible={true}
283
                height="25"
284
                width="25"
285
                color="#31649B"
286
                ariaLabel="grid-loading"
287
                radius="12.5"
288
                wrapperStyle={{}}
289
                wrapperClass="grid-wrapper"
290
              />
291
            </div>
292
          )}
293
          {!rows.length &&
662✔
294
            (loading ? (
190✔
295
              <div className="loading-spinner">
296
                <Grid
297
                  visible={true}
298
                  height="25"
299
                  width="25"
300
                  color="#31649B"
301
                  ariaLabel="grid-loading"
302
                  radius="12.5"
303
                  wrapperStyle={{}}
304
                  wrapperClass="grid-wrapper"
305
                />
306
              </div>
307
            ) : (
308
              <p className="rt-no-data">{noDataText || defaultNoDataText()}</p>
129✔
309
            ))}
310
        </div>
311
      </div>
312
    </div>
313
  );
314
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc