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

MarkUsProject / Markus / 18302290422

07 Oct 2025 04:43AM UTC coverage: 91.84% (-0.01%) from 91.852%
18302290422

Pull #7693

github

web-flow
Merge 74e05bf4e into 299cf6745
Pull Request #7693: Fix name column search in graders table

715 of 1506 branches covered (47.48%)

Branch coverage included in aggregate %.

0 of 4 new or added lines in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

42347 of 45382 relevant lines covered (93.31%)

119.91 hits per line

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

36.43
/app/javascript/Components/graders_manager.jsx
1
import React from "react";
2
import {createRoot} from "react-dom/client";
3
import {Tab, Tabs, TabList, TabPanel} from "react-tabs";
4
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
5

6
import {withSelection, CheckboxTable} from "./markus_with_selection_hoc";
7
import {selectFilter} from "./Helpers/table_helpers";
8
import {GraderDistributionModal} from "./Modals/graders_distribution_modal";
9
import {SectionDistributionModal} from "./Modals/section_distribution_modal";
10

11
class GradersManager extends React.Component {
12
  constructor(props) {
13
    super(props);
4✔
14
    this.state = {
4✔
15
      graders: [],
16
      groups: [],
17
      criteria: [],
18
      assign_graders_to_criteria: false,
19
      loading: true,
20
      tableName: "groups_table", // The first tab
21
      skip_empty_submissions: true,
22
      anonymize_groups: false,
23
      hide_unassigned_criteria: false,
24
      sections: {},
25
      isGraderDistributionModalOpen: false,
26
      isSectionDistributionModalOpen: false,
27
      show_hidden: false,
28
      show_hidden_groups: false,
29
      hidden_graders_count: 0,
30
      inactive_groups_count: 0,
31
    };
32
  }
33

34
  componentDidMount() {
35
    this.fetchData();
4✔
36
  }
37

38
  openGraderDistributionModal = () => {
4✔
39
    let groups = this.groupsTable ? this.groupsTable.state.selection : [];
×
40
    let criteria = this.criteriaTable ? this.criteriaTable.state.selection : [];
×
41
    let graders = this.gradersTable.state.selection;
×
42
    if (groups.length === 0 && criteria.length === 0) {
×
43
      alert(I18n.t("groups.select_a_group"));
×
44
      return;
×
45
    }
46

47
    if (graders.length === 0) {
×
48
      alert(I18n.t("graders.select_a_grader"));
×
49
      return;
×
50
    }
51

52
    this.setState({
×
53
      isGraderDistributionModalOpen: true,
54
    });
55
  };
56
  openSectionDistributionModal = () => {
4✔
57
    this.setState({
×
58
      isSectionDistributionModalOpen: true,
59
    });
60
  };
61

62
  fetchData = () => {
4✔
63
    fetch(Routes.course_assignment_graders_path(this.props.course_id, this.props.assignment_id), {
4✔
64
      headers: {
65
        Accept: "application/json",
66
      },
67
    })
68
      .then(response => {
69
        if (response.ok) {
4!
70
          return response.json();
4✔
71
        }
72
      })
73
      .then(res => {
74
        if (this.gradersTable) this.gradersTable.resetSelection();
4!
75
        if (this.groupsTable) this.groupsTable.resetSelection();
4!
76
        if (this.criteriaTable) this.criteriaTable.resetSelection();
4!
77

78
        let inactive_groups_count = 0;
4✔
79
        res.groups.forEach(group => {
4✔
80
          if (group.members.length && group.members.every(member => member[2])) {
12✔
81
            group.inactive = true;
4✔
82
            inactive_groups_count += 1;
4✔
83
          } else {
84
            group.inactive = false;
4✔
85
          }
86
        });
87
        this.setState({
4✔
88
          graders: res.graders,
89
          groups: res.groups,
90
          criteria: res.criteria,
91
          assign_graders_to_criteria: res.assign_graders_to_criteria,
92
          loading: false,
93
          sections: res.sections,
94
          anonymize_groups: res.anonymize_groups,
95
          hide_unassigned_criteria: res.hide_unassigned_criteria,
96
          isGraderDistributionModalOpen: false,
97
          isSectionDistributionModalOpen: false,
98
          hidden_graders_count: res.graders.filter(grader => grader.hidden).length,
×
99
          inactive_groups_count: inactive_groups_count,
100
        });
101
      });
102
  };
103

104
  assignAll = () => {
4✔
105
    let groups = this.groupsTable ? this.groupsTable.state.selection : [];
×
106
    let criteria = this.criteriaTable ? this.criteriaTable.state.selection : [];
×
107
    let graders = this.gradersTable.state.selection;
×
108

109
    if (groups.length === 0 && criteria.length === 0) {
×
110
      alert(I18n.t("groups.select_a_group"));
×
111
      return;
×
112
    }
113

114
    if (graders.length === 0) {
×
115
      alert(I18n.t("graders.select_a_grader"));
×
116
      return;
×
117
    }
118

119
    $.post({
×
120
      url: Routes.global_actions_course_assignment_graders_path(
121
        this.props.course_id,
122
        this.props.assignment_id
123
      ),
124
      data: {
125
        global_actions: "assign",
126
        current_table: this.state.tableName,
127
        skip_empty_submissions: this.state.skip_empty_submissions,
128
        groupings: groups,
129
        criteria: criteria,
130
        graders: graders,
131
      },
132
    }).then(this.fetchData);
133
  };
134

135
  assignSections = assignments => {
4✔
136
    let sections = Object.keys(assignments);
×
137
    let graders = Object.values(assignments);
×
138
    $.post({
×
139
      url: Routes.global_actions_course_assignment_graders_path(
140
        this.props.course_id,
141
        this.props.assignment_id
142
      ),
143
      data: {
144
        global_actions: "assign_sections",
145
        current_table: this.state.tableName,
146
        skip_empty_submissions: this.state.skip_empty_submissions,
147
        assignments: assignments,
148
        sections: sections,
149
        graders: graders,
150
      },
151
    }).then(this.fetchData);
152
  };
153

154
  assignRandomly = weightings => {
4✔
155
    let groups = this.groupsTable ? this.groupsTable.state.selection : [];
×
156
    let criteria = this.criteriaTable ? this.criteriaTable.state.selection : [];
×
157
    let graders = Object.keys(weightings);
×
158
    let weights = Object.values(weightings);
×
159

160
    $.post({
×
161
      url: Routes.global_actions_course_assignment_graders_path(
162
        this.props.course_id,
163
        this.props.assignment_id
164
      ),
165
      data: {
166
        global_actions: "random_assign",
167
        current_table: this.state.tableName,
168
        skip_empty_submissions: this.state.skip_empty_submissions,
169
        groupings: groups,
170
        criteria: criteria,
171
        graders: graders,
172
        weightings: weights,
173
      },
174
    }).then(this.fetchData);
175
  };
176

177
  unassignAll = () => {
4✔
178
    let groups = this.groupsTable ? this.groupsTable.state.selection : [];
×
179
    let criteria = this.criteriaTable ? this.criteriaTable.state.selection : [];
×
180
    let graders = this.gradersTable.state.selection;
×
181

182
    if (groups.length === 0 && criteria.length === 0) {
×
183
      alert(I18n.t("groups.select_a_group"));
×
184
      return;
×
185
    }
186

187
    if (graders.length === 0) {
×
188
      alert(I18n.t("graders.select_a_grader"));
×
189
      return;
×
190
    }
191

192
    $.post({
×
193
      url: Routes.global_actions_course_assignment_graders_path(
194
        this.props.course_id,
195
        this.props.assignment_id
196
      ),
197
      data: {
198
        global_actions: "unassign",
199
        current_table: this.state.tableName,
200
        groupings: groups,
201
        criteria: criteria,
202
        graders: graders,
203
      },
204
    }).then(this.fetchData);
205
  };
206

207
  unassignSingle = (id, grader_user_name, origin) => {
4✔
208
    let groups, criteria;
209
    if (origin === "groups_table") {
×
210
      groups = [id];
×
211
      criteria = [];
×
212
    } else {
213
      groups = [];
×
214
      criteria = [id];
×
215
    }
216

217
    $.post({
×
218
      url: Routes.global_actions_course_assignment_graders_path(
219
        this.props.course_id,
220
        this.props.assignment_id
221
      ),
222
      data: {
223
        global_actions: "unassign",
224
        current_table: origin,
225
        groupings: groups,
226
        criteria: criteria,
227
        grader_user_names: [grader_user_name],
228
      },
229
    }).then(this.fetchData);
230
  };
231

232
  toggleSkipEmptySubmissions = () => {
4✔
233
    this.setState({
×
234
      skip_empty_submissions: !this.state.skip_empty_submissions,
235
    });
236
  };
237

238
  toggleAssignGradersToCriteria = () => {
4✔
239
    const assign = !this.state.assign_graders_to_criteria;
×
240
    $.post({
×
241
      url: Routes.set_boolean_graders_options_course_assignment_path(
242
        this.props.course_id,
243
        this.props.assignment_id
244
      ),
245
      data: {
246
        attribute: {
247
          assignment_properties_attributes: {
248
            assign_graders_to_criteria: assign,
249
          },
250
        },
251
      },
252
    }).then(() => this.setState({assign_graders_to_criteria: assign}));
×
253
  };
254

255
  toggleAnonymizeGroups = () => {
4✔
256
    const assign = !this.state.anonymize_groups;
×
257
    $.post({
×
258
      url: Routes.set_boolean_graders_options_course_assignment_path(
259
        this.props.course_id,
260
        this.props.assignment_id
261
      ),
262
      data: {
263
        attribute: {
264
          assignment_properties_attributes: {anonymize_groups: assign},
265
        },
266
      },
267
    }).then(() => this.setState({anonymize_groups: assign}));
×
268
  };
269

270
  toggleHideUnassignedCriteria = () => {
4✔
271
    const assign = !this.state.hide_unassigned_criteria;
×
272
    $.post({
×
273
      url: Routes.set_boolean_graders_options_course_assignment_path(
274
        this.props.course_id,
275
        this.props.assignment_id
276
      ),
277
      data: {
278
        attribute: {
279
          assignment_properties_attributes: {
280
            hide_unassigned_criteria: assign,
281
          },
282
        },
283
      },
284
    }).then(() => this.setState({hide_unassigned_criteria: assign}));
×
285
  };
286

287
  getAssignedGraderObjects = () => {
4✔
288
    return this.state.graders.filter(grader => {
×
289
      return this.gradersTable.state.selection.includes(grader._id);
×
290
    });
291
  };
292

293
  renderHideUnassignedCriteria = () => {
4✔
294
    if (this.state.assign_graders_to_criteria) {
11!
295
      return (
×
296
        <div style={{marginBottom: "1em"}}>
297
          <label>
298
            <input
299
              type="checkbox"
300
              checked={this.state.hide_unassigned_criteria}
301
              onChange={this.toggleHideUnassignedCriteria}
302
              style={{marginRight: "5px"}}
303
            />
304
            {I18n.t("graders.hide_unassigned_criteria")}
305
          </label>
306
        </div>
307
      );
308
    }
309
  };
310

311
  onSelectTable = index => {
4✔
312
    if (index === 0) {
×
313
      this.setState({tableName: "groups_table"});
×
314
    } else {
315
      this.setState({tableName: "criteria_table"});
×
316
    }
317
  };
318

319
  toggleShowHidden = event => {
4✔
320
    let show_hidden = event.target.checked;
×
321
    this.setState({show_hidden});
×
322
  };
323

324
  toggleShowHiddenGroups = event => {
4✔
325
    let show_hidden_groups = event.target.checked;
3✔
326
    this.setState({show_hidden_groups});
3✔
327
  };
328

329
  render() {
330
    return (
11✔
331
      <div>
332
        <GradersActionBox
333
          assignAll={this.assignAll}
334
          openGraderDistributionModal={this.openGraderDistributionModal}
335
          openSectionDistributionModal={this.openSectionDistributionModal}
336
          unassignAll={this.unassignAll}
337
          showHidden={this.state.show_hidden}
338
          showHiddenGroups={this.state.show_hidden_groups}
339
          updateShowHidden={this.toggleShowHidden}
340
          updateShowHiddenGroups={this.toggleShowHiddenGroups}
341
          hiddenGradersCount={this.state.loading ? null : this.state.hidden_graders_count}
11✔
342
          hiddenGroupsCount={this.state.loading ? null : this.state.inactive_groups_count}
11✔
343
        />
344
        <div className="mapping-tables">
345
          <div className="mapping-table">
346
            <GradersTable
347
              ref={r => (this.gradersTable = r)}
22✔
348
              graders={this.state.graders}
349
              loading={this.state.loading}
350
              assign_graders_to_criteria={this.state.assign_graders_to_criteria}
351
              numCriteria={this.state.criteria.length}
352
              showHidden={this.state.show_hidden}
353
            />
354
          </div>
355
          <div className="mapping-table">
356
            <Tabs onSelect={this.onSelectTable}>
357
              <TabList>
358
                <Tab>{I18n.t("activerecord.models.group.other")}</Tab>
359
                <Tab>{I18n.t("activerecord.models.criterion.other")}</Tab>
360
              </TabList>
361

362
              <TabPanel>
363
                <div style={{marginBottom: "1em"}}>
364
                  <label>
365
                    <input
366
                      type="checkbox"
367
                      checked={!this.state.skip_empty_submissions}
368
                      onChange={this.toggleSkipEmptySubmissions}
369
                      style={{marginRight: "5px"}}
370
                    />
371
                    {I18n.t("graders.skip_empty_submissions")}
372
                  </label>
373
                  <div className="inline-help">
374
                    <p>{I18n.t("graders.skip_empty_submissions_tooltip")}</p>
375
                  </div>
376
                </div>
377
                <div style={{marginBottom: "1em"}}>
378
                  <label>
379
                    <input
380
                      type="checkbox"
381
                      checked={this.state.anonymize_groups}
382
                      onChange={this.toggleAnonymizeGroups}
383
                      style={{marginRight: "5px"}}
384
                    />
385
                    {I18n.t("graders.anonymize_groups")}
386
                  </label>
387
                </div>
388
                <GroupsTable
389
                  ref={r => (this.groupsTable = r)}
22✔
390
                  groups={this.state.groups}
391
                  loading={this.state.loading}
392
                  unassignSingle={this.unassignSingle}
393
                  showSections={this.props.showSections}
394
                  sections={this.state.sections}
395
                  numCriteria={this.state.criteria.length}
396
                  showCoverage={this.state.assign_graders_to_criteria}
397
                  showInactive={this.state.show_hidden_groups}
398
                />
399
              </TabPanel>
400
              <TabPanel>
401
                <div style={{marginBottom: "1em"}}>
402
                  <label>
403
                    <input
404
                      type="checkbox"
405
                      onChange={this.toggleAssignGradersToCriteria}
406
                      checked={this.state.assign_graders_to_criteria}
407
                      style={{marginRight: "5px"}}
408
                    />
409
                    {I18n.t("graders.assign_to_criteria")}
410
                  </label>
411
                </div>
412
                {this.renderHideUnassignedCriteria()}
413
                <CriteriaTable
414
                  display={this.state.assign_graders_to_criteria}
415
                  ref={r => (this.criteriaTable = r)}
×
416
                  criteria={this.state.criteria}
417
                  loading={this.state.loading}
418
                  unassignSingle={this.unassignSingle}
419
                  numGroups={this.state.groups.length}
420
                />
421
              </TabPanel>
422
            </Tabs>
423
          </div>
424
        </div>
425
        {this.state.isGraderDistributionModalOpen && (
11!
426
          <GraderDistributionModal
427
            isOpen={this.state.isGraderDistributionModalOpen}
428
            onRequestClose={() =>
429
              this.setState({
×
430
                isGraderDistributionModalOpen: false,
431
              })
432
            }
433
            graders={this.getAssignedGraderObjects()}
434
            onSubmit={this.assignRandomly}
435
          />
436
        )}
437
        {this.state.isSectionDistributionModalOpen && (
11!
438
          <SectionDistributionModal
439
            isOpen={this.state.isSectionDistributionModalOpen}
440
            onRequestClose={() => this.setState({isSectionDistributionModalOpen: false})}
×
441
            onSubmit={this.assignSections}
442
            graders={this.state.graders}
443
            sections={this.state.sections}
444
          />
445
        )}
446
      </div>
447
    );
448
  }
449
}
450

451
class RawGradersTable extends React.Component {
452
  constructor(props) {
453
    super(props);
4✔
454
    this.state = {
4✔
455
      filtered: [],
456
    };
457
  }
458

459
  getColumns = () => [
11✔
460
    {
461
      accessor: "hidden",
462
      id: "hidden",
463
      width: 0,
464
      className: "rt-hidden",
465
      headerClassName: "rt-hidden",
466
      resizable: false,
467
    },
468
    {
469
      show: false,
470
      accessor: "_id",
471
      id: "_id",
472
    },
473
    {
474
      Header: I18n.t("activerecord.attributes.user.user_name"),
475
      accessor: "user_name",
476
      id: "user_name",
477
      Cell: props =>
478
        props.original.hidden
×
479
          ? `${props.value} (${I18n.t("activerecord.attributes.user.hidden")})`
480
          : props.value,
481
      filterMethod: (filter, row) => {
482
        if (filter.value) {
×
483
          return `${row._original.user_name}${
×
484
            row._original.hidden ? `, ${I18n.t("activerecord.attributes.user.hidden")}` : ""
×
485
          }`.includes(filter.value);
486
        } else {
487
          return true;
×
488
        }
489
      },
490
      sortable: true,
491
      minWidth: 90,
492
    },
493
    {
494
      Header: I18n.t("activerecord.attributes.user.full_name"),
495
      accessor: "full_name",
496
      id: "full_name",
497
      filterable: true,
UNCOV
498
      Cell: row => `${row.original.first_name} ${row.original.last_name}`,
×
499
      filterMethod: (filter, row) => {
NEW
500
        if (filter.value) {
×
NEW
501
          const fullName = `${row._original.first_name} ${row._original.last_name}`.toLowerCase();
×
NEW
502
          return fullName.includes(filter.value.toLowerCase());
×
503
        } else {
NEW
504
          return true;
×
505
        }
506
      },
507
      sortable: true,
508
      minWidth: 170,
509
    },
510
    {
511
      Header: I18n.t("activerecord.models.group.other"),
512
      accessor: "groups",
513
      className: "number",
514
      filterable: false,
515
    },
516
    {
517
      Header: I18n.t("activerecord.models.criterion.other"),
518
      accessor: "criteria",
519
      filterable: false,
520
      Cell: ({value}) => {
521
        if (this.props.assign_graders_to_criteria) {
×
522
          return (
×
523
            <span>
524
              {value}/{this.props.numCriteria}
525
            </span>
526
          );
527
        } else {
528
          return I18n.t("all");
×
529
        }
530
      },
531
    },
532
  ];
533

534
  static getDerivedStateFromProps(props, state) {
535
    let filtered = [];
11✔
536
    for (let i = 0; i < state.filtered.length; i++) {
11✔
537
      if (state.filtered[i].id !== "hidden") {
7!
538
        filtered.push(state.filtered[i]);
×
539
      }
540
    }
541
    if (!props.showHidden) {
11!
542
      filtered.push({id: "hidden", value: false});
11✔
543
    }
544
    return {filtered};
11✔
545
  }
546

547
  onFilteredChange = filtered => {
4✔
548
    this.setState({filtered});
×
549
  };
550

551
  render() {
552
    return (
11✔
553
      <CheckboxTable
554
        ref={r => (this.checkboxTable = r)}
22✔
555
        data={this.props.graders}
556
        columns={this.getColumns()}
557
        defaultSorted={[
558
          {
559
            id: "user_name",
560
          },
561
        ]}
562
        loading={this.props.loading}
563
        filterable
564
        filtered={this.state.filtered}
565
        onFilteredChange={this.onFilteredChange}
566
        {...this.props.getCheckboxProps()}
567
      />
568
    );
569
  }
570
}
571

572
class RawGroupsTable extends React.Component {
573
  constructor(props) {
574
    super(props);
4✔
575
    this.state = {
4✔
576
      filtered: [],
577
    };
578
  }
579

580
  getColumns = () => {
4✔
581
    return [
11✔
582
      {
583
        accessor: "inactive",
584
        id: "inactive",
585
        width: 0,
586
        className: "rt-hidden",
587
        headerClassName: "rt-hidden",
588
        resizable: false,
589
      },
590
      {
591
        show: false,
592
        accessor: "_id",
593
        id: "_id",
594
      },
595
      {
596
        Header: I18n.t("activerecord.models.section", {count: 1}),
597
        accessor: "section",
598
        id: "section",
599
        show: this.props.showSections || false,
22✔
600
        minWidth: 70,
601
        Cell: ({value}) => {
602
          return this.props.sections[value] || "";
×
603
        },
604
        filterMethod: (filter, row) => {
605
          if (filter.value === "all") {
×
606
            return true;
×
607
          } else {
608
            return this.props.sections[row[filter.id]] === filter.value;
×
609
          }
610
        },
611
        Filter: selectFilter,
612
        filterOptions: Object.entries(this.props.sections).map(kv => ({
×
613
          value: kv[1],
614
          text: kv[1],
615
        })),
616
      },
617
      {
618
        Header: I18n.t("activerecord.models.group.one"),
619
        accessor: "group_name",
620
        id: "group_name",
621
        minWidth: 150,
622
      },
623
      {
624
        Header: I18n.t("activerecord.models.ta.other"),
625
        accessor: "graders",
626
        Cell: row => {
627
          return row.value.map(ta_data => (
13✔
628
            <div key={`${row.original._id}-${ta_data.grader}`}>
×
629
              {ta_data.hidden
×
630
                ? `${ta_data.grader} (${I18n.t("activerecord.attributes.user.hidden")})`
631
                : ta_data.grader}
632
              <a
633
                href="#"
634
                onClick={() =>
635
                  this.props.unassignSingle(row.original._id, ta_data.grader, "groups_table")
×
636
                }
637
                title={I18n.t("graders.actions.unassign_grader")}
638
              >
639
                <FontAwesomeIcon icon="fa-solid fa-trash" className="icon-right" />
640
              </a>
641
            </div>
642
          ));
643
        },
644
        filterable: false,
645
        minWidth: 100,
646
      },
647
      {
648
        Header: I18n.t("graders.coverage"),
649
        accessor: "criteria_coverage_count",
650
        Cell: ({value}) => (
651
          <span>
×
652
            {value || 0}/{this.props.numCriteria}
×
653
          </span>
654
        ),
655
        minWidth: 70,
656
        className: "number",
657
        filterable: false,
658
        show: this.props.showCoverage,
659
      },
660
    ];
661
  };
662

663
  static getDerivedStateFromProps(props, state) {
664
    let filtered = state.filtered.filter(group => group.id !== "inactive");
11✔
665

666
    if (!props.showInactive) {
11✔
667
      filtered.push({id: "inactive", value: false});
9✔
668
    }
669
    return {filtered};
11✔
670
  }
671

672
  onFilteredChange = filtered => {
4✔
673
    this.setState({filtered});
×
674
  };
675

676
  render() {
677
    return (
11✔
678
      <CheckboxTable
679
        ref={r => (this.checkboxTable = r)}
22✔
680
        data={this.props.groups}
681
        columns={this.getColumns()}
682
        defaultSorted={[
683
          {
684
            id: "group_name",
685
          },
686
        ]}
687
        loading={this.props.loading}
688
        filterable
689
        filtered={this.state.filtered}
690
        onFilteredChange={this.onFilteredChange}
691
        {...this.props.getCheckboxProps()}
692
      />
693
    );
694
  }
695
}
696

697
class RawCriteriaTable extends React.Component {
698
  getColumns = () => {
×
699
    return [
×
700
      {
701
        show: false,
702
        accessor: "_id",
703
        id: "_id",
704
      },
705
      {
706
        Header: I18n.t("activerecord.attributes.criterion.name"),
707
        accessor: "name",
708
        minWidth: 150,
709
      },
710
      {
711
        Header: I18n.t("activerecord.models.ta.other"),
712
        accessor: "graders",
713
        Cell: row => {
714
          return row.value.map(ta_data => (
×
715
            <div key={`${row.original._id}-${ta_data.grader}`}>
×
716
              {ta_data.hidden
×
717
                ? `${ta_data.grader} (${I18n.t("activerecord.attributes.user.hidden")})`
718
                : ta_data.grader}
719
              <a
720
                href="#"
721
                onClick={() =>
722
                  this.props.unassignSingle(row.original._id, ta_data.grader, "criteria_table")
×
723
                }
724
                title={I18n.t("graders.actions.unassign_grader")}
725
              >
726
                <FontAwesomeIcon icon="fa-solid fa-trash" className="icon-right" />
727
              </a>
728
            </div>
729
          ));
730
        },
731
        filterable: false,
732
        minWidth: 70,
733
      },
734
      {
735
        Header: I18n.t("graders.coverage"),
736
        accessor: "coverage",
737
        Cell: ({value}) => (
738
          <span>
×
739
            {value}/{this.props.numGroups}
740
          </span>
741
        ),
742
        minWidth: 70,
743
        className: "number",
744
        filterable: false,
745
      },
746
    ];
747
  };
748

749
  render() {
750
    if (this.props.display) {
×
751
      return (
×
752
        <CheckboxTable
753
          ref={r => (this.checkboxTable = r)}
×
754
          data={this.props.criteria}
755
          columns={this.getColumns()}
756
          defaultSorted={[
757
            {
758
              id: "_id",
759
            },
760
          ]}
761
          loading={this.props.loading}
762
          filterable
763
          {...this.props.getCheckboxProps()}
764
        />
765
      );
766
    } else {
767
      return null;
×
768
    }
769
  }
770
}
771

772
const GradersTable = withSelection(RawGradersTable);
1✔
773
const GroupsTable = withSelection(RawGroupsTable);
1✔
774
const CriteriaTable = withSelection(RawCriteriaTable);
1✔
775

776
class GradersActionBox extends React.Component {
777
  render = () => {
4✔
778
    let showHiddenGraderTooltip = "";
11✔
779
    let showHiddenGroupsTooltip = "";
11✔
780
    if (this.props.hiddenGradersCount !== null && this.props.hiddenGroupsCount !== null) {
11✔
781
      showHiddenGraderTooltip = `${I18n.t("graders.inactive_graders_count", {
7✔
782
        count: this.props.hiddenGradersCount,
783
      })}`;
784
      showHiddenGroupsTooltip = `${I18n.t("activerecord.attributes.grouping.inactive_groups", {
7✔
785
        count: this.props.hiddenGroupsCount,
786
      })}`;
787
    }
788

789
    return (
11✔
790
      <div className="rt-action-box">
791
        <span className={"flex-row-expand"}>
792
          <input
793
            id="show_hidden"
794
            name="show_hidden"
795
            type="checkbox"
796
            checked={this.props.showHidden}
797
            onChange={this.props.updateShowHidden}
798
            className={"hide-user-checkbox"}
799
          />
800
          <label title={showHiddenGraderTooltip} htmlFor="show_hidden">
801
            {I18n.t("tas.display_inactive")}
802
          </label>
803
        </span>
804
        <span>
805
          <input
806
            id="show_hidden_groups"
807
            name="show_hidden_groups"
808
            type="checkbox"
809
            checked={this.props.showHiddenGroups}
810
            onChange={this.props.updateShowHiddenGroups}
811
            className={"hide-user-checkbox"}
812
            data-testid={"show_hidden_groups"}
813
          />
814
          <label
815
            title={showHiddenGroupsTooltip}
816
            htmlFor="show_hidden_groups"
817
            data-testid={"show_hidden_groups_tooltip"}
818
          >
819
            {I18n.t("groups.display_inactive")}
820
          </label>
821
        </span>
822
        <button onClick={this.props.assignAll}>
823
          <FontAwesomeIcon icon="fa-solid fa-user-plus" />
824
          {I18n.t("graders.actions.assign_grader")}
825
        </button>
826
        <button onClick={this.props.openGraderDistributionModal}>
827
          <FontAwesomeIcon icon="fa-solid fa-dice" />
828
          {I18n.t("graders.actions.randomly_assign_graders")}
829
        </button>
830
        <button onClick={this.props.openSectionDistributionModal}>
831
          <FontAwesomeIcon icon="fa-solid fa-list" />
832
          {I18n.t("graders.actions.assign_by_section")}
833
        </button>
834
        <button onClick={this.props.unassignAll}>
835
          <FontAwesomeIcon icon="fa-solid fa-user-minus" />
836
          {I18n.t("graders.actions.unassign_grader")}
837
        </button>
838
      </div>
839
    );
840
  };
841
}
842

843
export function makeGradersManager(elem, props) {
844
  const root = createRoot(elem);
×
845
  root.render(<GradersManager {...props} />);
×
846
}
847
export {GradersManager};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc