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

MarkUsProject / Markus / 18783844054

24 Oct 2025 03:06PM UTC coverage: 91.574% (+0.06%) from 91.516%
18783844054

Pull #7697

github

web-flow
Merge d198c3871 into 22805cd2d
Pull Request #7697: Add scheduled visibility for assessments

787 of 1638 branches covered (48.05%)

Branch coverage included in aggregate %.

193 of 198 new or added lines in 10 files covered. (97.47%)

55 existing lines in 4 files now uncovered.

42751 of 45906 relevant lines covered (93.13%)

121.25 hits per line

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

41.64
/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, textFilter} 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);
12✔
14
    this.state = {
12✔
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();
12✔
36
  }
37

38
  openGraderDistributionModal = () => {
12✔
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 = () => {
12✔
57
    this.setState({
×
58
      isSectionDistributionModalOpen: true,
59
    });
60
  };
61

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

78
        let inactive_groups_count = 0;
12✔
79
        res.groups.forEach(group => {
12✔
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({
12✔
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,
24✔
99
          inactive_groups_count: inactive_groups_count,
100
        });
101
      });
102
  };
103

104
  assignAll = () => {
12✔
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 => {
12✔
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 => {
12✔
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 = () => {
12✔
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) => {
12✔
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 = () => {
12✔
233
    this.setState({
×
234
      skip_empty_submissions: !this.state.skip_empty_submissions,
235
    });
236
  };
237

238
  toggleAssignGradersToCriteria = () => {
12✔
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 = () => {
12✔
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 = () => {
12✔
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 = () => {
12✔
288
    return this.state.graders.filter(grader => {
×
289
      return this.gradersTable.state.selection.includes(grader._id);
×
290
    });
291
  };
292

293
  renderHideUnassignedCriteria = () => {
12✔
294
    if (this.state.assign_graders_to_criteria) {
27!
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 => {
12✔
312
    if (index === 0) {
×
313
      this.setState({tableName: "groups_table"});
×
314
    } else {
315
      this.setState({tableName: "criteria_table"});
×
316
    }
317
  };
318

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

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

329
  render() {
330
    return (
27✔
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}
27✔
342
          hiddenGroupsCount={this.state.loading ? null : this.state.inactive_groups_count}
27✔
343
        />
344
        <div className="mapping-tables">
345
          <div className="mapping-table">
346
            <GradersTable
347
              ref={r => (this.gradersTable = r)}
54✔
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)}
54✔
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 && (
27!
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 && (
27!
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);
12✔
454
    this.state = {
12✔
455
      filtered: [],
456
    };
457
  }
458

459
  getColumns = () => [
33✔
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
39!
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,
498
      Filter: textFilter,
499
      Cell: row => `${row.original.first_name} ${row.original.last_name}`,
39✔
500
      filterMethod: (filter, row) => {
501
        if (filter.value) {
36!
502
          const fullName = `${row._original.first_name} ${row._original.last_name}`.toLowerCase();
36✔
503
          return fullName.includes(filter.value.toLowerCase());
36✔
504
        } else {
UNCOV
505
          return true;
×
506
        }
507
      },
508
      sortable: true,
509
      minWidth: 170,
510
    },
511
    {
512
      Header: I18n.t("activerecord.models.group.other"),
513
      accessor: "groups",
514
      className: "number",
515
      filterable: false,
516
    },
517
    {
518
      Header: I18n.t("activerecord.models.criterion.other"),
519
      accessor: "criteria",
520
      filterable: false,
521
      Cell: ({value}) => {
522
        if (this.props.assign_graders_to_criteria) {
39!
UNCOV
523
          return (
×
524
            <span>
525
              {value}/{this.props.numCriteria}
526
            </span>
527
          );
528
        } else {
529
          return I18n.t("all");
39✔
530
        }
531
      },
532
    },
533
  ];
534

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

548
  onFilteredChange = filtered => {
12✔
549
    this.setState({filtered});
6✔
550
  };
551

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

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

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

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

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

673
  onFilteredChange = filtered => {
12✔
UNCOV
674
    this.setState({filtered});
×
675
  };
676

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

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

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

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

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

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

844
export function makeGradersManager(elem, props) {
845
  const root = createRoot(elem);
×
UNCOV
846
  root.render(<GradersManager {...props} />);
×
847
}
848
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

© 2025 Coveralls, Inc