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

MarkUsProject / Markus / 12976781475

26 Jan 2025 05:20PM UTC coverage: 91.854% (-0.02%) from 91.87%
12976781475

Pull #7392

github

web-flow
Merge 4e5b55b4a into 10a758445
Pull Request #7392: Update React to v18

624 of 1361 branches covered (45.85%)

Branch coverage included in aggregate %.

0 of 16 new or added lines in 8 files covered. (0.0%)

75 existing lines in 4 files now uncovered.

41290 of 44270 relevant lines covered (93.27%)

120.23 hits per line

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

41.25
/app/javascript/Components/submission_table.jsx
1
import React from "react";
2
import {createRoot} from "react-dom/client";
3
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
4

5
import {CheckboxTable, withSelection} from "./markus_with_selection_hoc";
6
import {
7
  dateSort,
8
  markingStateColumn,
9
  selectFilter,
10
  getMarkingStates,
11
} from "./Helpers/table_helpers";
12
import CollectSubmissionsModal from "./Modals/collect_submissions_modal";
13
import ReleaseUrlsModal from "./Modals/release_urls_modal";
14
import consumer from "../channels/consumer";
15
import {renderFlashMessages} from "../common/flash";
16

17
class RawSubmissionTable extends React.Component {
18
  constructor() {
19
    super();
4✔
20
    const markingStates = getMarkingStates([]);
4✔
21
    this.state = {
4✔
22
      groupings: [],
23
      sections: {},
24
      loading: true,
25
      showCollectSubmissionsModal: false,
26
      showReleaseUrlsModal: false,
27
      marking_states: markingStates,
28
      markingStateFilter: "all",
29
      inactiveGroupsCount: 0,
30
      filtered: [],
31
    };
32
  }
33

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

39
  fetchData = () => {
4✔
40
    fetch(
4✔
41
      Routes.course_assignment_submissions_path(this.props.course_id, this.props.assignment_id),
42
      {
43
        headers: {
44
          Accept: "application/json",
45
        },
46
      }
47
    )
48
      .then(response => {
49
        if (response.ok) {
4!
50
          return response.json();
4✔
51
        }
52
      })
53
      .then(res => {
54
        this.props.resetSelection();
4✔
55

56
        let inactive_groups_count = 0;
4✔
57
        res.groupings.forEach(group => {
4✔
58
          if (group.members.length && group.members.every(member => member[1])) {
12✔
59
            group.inactive = true;
4✔
60
            inactive_groups_count++;
4✔
61
          } else {
62
            group.inactive = false;
4✔
63
          }
64
        });
65

66
        this.toggleShowInactiveGroups(false);
4✔
67

68
        const markingStates = getMarkingStates(res.groupings);
4✔
69

70
        this.setState({
4✔
71
          groupings: res.groupings,
72
          sections: res.sections,
73
          loading: false,
74
          marking_states: markingStates,
75
          inactiveGroupsCount: inactive_groups_count,
76
        });
77
      });
78
  };
79

80
  onFilteredChange = (filtered, column) => {
4✔
81
    const summaryTable = this.checkboxTable.getWrappedInstance();
×
82
    if (column.id != "marking_state") {
×
83
      const markingStates = getMarkingStates(summaryTable.state.sortedData);
×
84
      this.setState({marking_states: markingStates});
×
85
    } else {
86
      const markingStateFilter = filtered.find(filter => filter.id == "marking_state").value;
×
87
      this.setState({markingStateFilter: markingStateFilter});
×
88
    }
89

90
    this.setState({filtered});
×
91
  };
92

93
  groupNameWithMembers = row => {
4✔
94
    let members = "";
13✔
95
    if (
13!
96
      !row.original.members ||
26!
97
      (row.original.members.length === 1 && row.value === row.original.members[0])
98
    ) {
99
      members = "";
×
100
    } else {
101
      members = ` (${row.original.members.map(m => m[0]).join(", ")})`;
26✔
102
    }
103
    return row.value + members;
13✔
104
  };
105

106
  groupNameFilter = (filter, row) => {
4✔
107
    if (filter.value) {
×
108
      // Check group name
109
      if (row._original.group_name.includes(filter.value)) {
×
110
        return true;
×
111
      }
112
      // Check member names
113
      return (
×
114
        row._original.members && row._original.members.some(name => name.includes(filter.value))
×
115
      );
116
    } else {
117
      return true;
×
118
    }
119
  };
120

121
  columns = () => [
11✔
122
    {
123
      show: false,
124
      accessor: "inactive",
125
      id: "inactive",
126
    },
127
    {
128
      show: false,
129
      accessor: "_id",
130
      id: "_id",
131
    },
132
    {
133
      Header: I18n.t("activerecord.models.group.one"),
134
      accessor: "group_name",
135
      id: "group_name",
136
      Cell: row => {
137
        const group_name = this.groupNameWithMembers(row);
13✔
138
        if (row.original.result_id) {
13!
139
          const result_url = Routes.edit_course_result_path(
13✔
140
            this.props.course_id,
141
            row.original.result_id
142
          );
143
          return <a href={result_url}>{group_name}</a>;
13✔
144
        } else {
145
          return group_name;
×
146
        }
147
      },
148
      minWidth: 170,
149
      filterMethod: this.groupNameFilter,
150
    },
151
    {
152
      Header: I18n.t("submissions.repo_browser.repository"),
153
      filterable: false,
154
      sortable: false,
155
      Cell: row => {
156
        return (
13✔
157
          <a
158
            href={Routes.repo_browser_course_assignment_submissions_path(
159
              this.props.course_id,
160
              this.props.assignment_id,
161
              {
162
                grouping_id: row.original._id,
163
              }
164
            )}
165
          >
166
            {row.original.group_name}
167
          </a>
168
        );
169
      },
170
      minWidth: 80,
171
    },
172
    {
173
      Header: I18n.t("activerecord.models.section", {count: 1}),
174
      accessor: "section",
175
      id: "section",
176
      show: this.props.show_sections,
177
      minWidth: 70,
178
      filterMethod: (filter, row) => {
179
        if (filter.value === "all") {
×
180
          return true;
×
181
        } else {
182
          return filter.value === row[filter.id];
×
183
        }
184
      },
185
      Filter: selectFilter,
186
      filterOptions: Object.entries(this.state.sections).map(kv => ({
×
187
        value: kv[1],
188
        text: kv[1],
189
      })),
190
    },
191
    {
192
      show: this.props.is_timed,
193
      Header: I18n.t("activerecord.attributes.assignment.start_time"),
194
      accessor: "start_time",
195
      filterable: false,
196
      sortMethod: dateSort,
197
    },
198
    {
199
      Header: I18n.t("submissions.commit_date"),
200
      accessor: "submission_time",
201
      id: "submission_time",
202
      filterable: true,
203
      sortMethod: dateSort,
204
      Filter: selectFilter,
205
      filterMethod: (filter, row) => {
206
        if (filter.value === "all") {
×
207
          return true;
×
208
        } else if (filter.value === "false") {
×
209
          return !row[filter.id];
×
210
        } else {
211
          return !!row[filter.id];
×
212
        }
213
      },
214
      filterOptions: [
215
        {
216
          value: "true",
217
          text: I18n.t("submissions.commit_date_filter.collected"),
218
        },
219
        {
220
          value: "false",
221
          text: I18n.t("submissions.commit_date_filter.not_collected"),
222
        },
223
      ],
224
    },
225
    {
226
      Header: I18n.t("submissions.grace_credits_used"),
227
      accessor: "grace_credits_used",
228
      show: this.props.show_grace_tokens,
229
      minWidth: 100,
230
      style: {textAlign: "right"},
231
    },
232
    markingStateColumn(this.state.marking_states, this.state.markingStateFilter, {minWidth: 70}),
233
    {
234
      Header: I18n.t("results.total_mark"),
235
      accessor: "final_grade",
236
      Cell: row => {
237
        const value =
238
          row.original.final_grade === undefined
13!
239
            ? "-"
240
            : Math.round(row.original.final_grade * 100) / 100;
241
        const max_mark = Math.round(row.original.max_mark * 100) / 100;
13✔
242
        return value + " / " + max_mark;
13✔
243
      },
244
      className: "number",
245
      minWidth: 80,
246
      filterable: false,
247
      defaultSortDesc: true,
248
    },
249
    {
250
      Header: I18n.t("activerecord.models.tag.other"),
251
      accessor: "tags",
252
      Cell: row => (
253
        <ul className="tag-list">
13✔
254
          {row.original.tags.map(tag => (
255
            <li key={`${row.original._id}-${tag}`} className="tag-element">
×
256
              {tag}
257
            </li>
258
          ))}
259
        </ul>
260
      ),
261
      minWidth: 80,
262
      sortable: false,
263
    },
264
  ];
265

266
  // Custom getTrProps function to highlight submissions that have been collected.
267
  getTrProps = (state, ri, ci, instance) => {
4✔
268
    if (
360✔
269
      ri === undefined ||
399✔
270
      ri.original.marking_state === undefined ||
271
      ri.original.marking_state === "not_collected" ||
272
      ri.original.marking_state === "before_due_date"
273
    ) {
274
      return {ri};
347✔
275
    } else {
276
      return {
13✔
277
        ri,
278
        style: {
279
          background: document.documentElement.style.getPropertyValue("--light_success"),
280
        },
281
      };
282
    }
283
  };
284

285
  // Submission table actions
286
  collectSubmissions = (override, collect_current, apply_late_penalty, retain_existing_grading) => {
4✔
287
    this.setState({showCollectSubmissionsModal: false});
×
288
    $.post({
×
289
      url: Routes.collect_submissions_course_assignment_submissions_path(
290
        this.props.course_id,
291
        this.props.assignment_id
292
      ),
293
      data: {
294
        groupings: this.props.selection,
295
        override: override,
296
        collect_current: collect_current,
297
        apply_late_penalty: apply_late_penalty,
298
        retain_existing_grading: retain_existing_grading,
299
      },
300
    });
301
  };
302

303
  uncollectAllSubmissions = () => {
4✔
304
    if (!window.confirm(I18n.t("submissions.collect.undo_results_loss_warning"))) {
×
305
      return;
×
306
    }
307

308
    $.get({
×
309
      url: Routes.uncollect_all_submissions_course_assignment_submissions_path(
310
        this.props.course_id,
311
        this.props.assignment_id
312
      ),
313
      data: {groupings: this.props.selection},
314
    });
315
  };
316

317
  setMarkingStates = marking_state => {
4✔
318
    $.post({
×
319
      url: Routes.set_result_marking_state_course_assignment_submissions_path(
320
        this.props.course_id,
321
        this.props.assignment_id
322
      ),
323
      data: {
324
        groupings: this.props.selection,
325
        marking_state: marking_state,
326
      },
327
    }).then(this.fetchData);
328
  };
329

330
  prepareGroupingFiles = print => {
4✔
331
    if (window.confirm(I18n.t("submissions.marking_incomplete_warning"))) {
×
332
      $.post({
×
333
        url: Routes.zip_groupings_files_course_assignment_submissions_url(
334
          this.props.course_id,
335
          this.props.assignment_id
336
        ),
337
        data: {
338
          groupings: this.props.selection,
339
          print: print,
340
        },
341
      });
342
    }
343
  };
344

345
  runTests = () => {
4✔
346
    $.post({
×
347
      url: Routes.run_tests_course_assignment_submissions_path(
348
        this.props.course_id,
349
        this.props.assignment_id
350
      ),
351
      data: {groupings: this.props.selection},
352
    });
353
  };
354

355
  toggleRelease = released => {
4✔
356
    this.setState({loading: true}, () => {
×
357
      $.post({
×
358
        url: Routes.update_submissions_course_assignment_submissions_path(
359
          this.props.course_id,
360
          this.props.assignment_id
361
        ),
362
        data: {
363
          release_results: released,
364
          groupings: this.props.selection,
365
        },
366
      })
367
        .then(this.fetchData)
368
        .catch(this.fetchData);
369
    });
370
  };
371

372
  refreshViewTokens = (updated_tokens, after_function) => {
4✔
373
    this.setState(prevState => {
×
374
      prevState.groupings.forEach(row => {
×
375
        if (updated_tokens[row.result_id]) {
×
376
          row["result_view_token"] = updated_tokens[row.result_id];
×
377
        }
378
      });
379
      return prevState;
×
380
    }, after_function);
381
  };
382

383
  refreshViewTokenExpiry = (updated_tokens, after_function) => {
4✔
384
    this.setState(prevState => {
×
385
      prevState.groupings.forEach(row => {
×
386
        if (updated_tokens[row.result_id] !== undefined) {
×
387
          row["result_view_token_expiry"] = updated_tokens[row.result_id];
×
388
        }
389
      });
390
      return prevState;
×
391
    }, after_function);
392
  };
393

394
  createChannelSubscriptions = () => {
4✔
395
    // Subscribe to submission collection job status
396
    consumer.subscriptions.create(
4✔
397
      {channel: "CollectSubmissionsChannel", course_id: this.props.course_id},
398
      {
399
        connected: () => {},
400
        disconnected: () => {},
401
        received: data => {
402
          // Called when there's incoming data on the websocket for this channel
403
          if (data["status"] != null) {
×
404
            let message_data = generateMessage(data);
×
405
            renderFlashMessages(message_data);
×
406
          }
407
          if (data["update_table"] != null) {
×
408
            this.fetchData();
×
409
          }
410
        },
411
      }
412
    );
413

414
    // Subscribe to autotest runs job status
415
    consumer.subscriptions.create(
4✔
416
      {
417
        channel: "TestRunsChannel",
418
        course_id: this.props.course_id,
419
        assignment_id: this.props.assignment_id,
420
      },
421
      {
422
        connected: () => {},
423
        disconnected: () => {},
424
        received: data => {
425
          // Called when there's incoming data on the websocket for this channel
426
          if (data["status"] != null) {
×
427
            let message_data = generateMessage(data);
×
428
            renderFlashMessages(message_data);
×
429
          }
430
          if (
×
431
            data["update_table"] !== undefined &&
×
432
            data["assignment_ids"] !== undefined &&
433
            data["assignment_ids"].includes(this.props.assignment_id)
434
          ) {
435
            this.fetchData();
×
436
          }
437
        },
438
      }
439
    );
440
  };
441

442
  toggleShowInactiveGroups = showInactiveGroups => {
4✔
443
    let filtered = this.state.filtered.filter(group => {
7✔
444
      group.id !== "inactive";
2✔
445
    });
446

447
    if (!showInactiveGroups) {
7✔
448
      filtered.push({id: "inactive", value: false});
5✔
449
    }
450

451
    this.setState({filtered});
7✔
452
  };
453

454
  render() {
455
    const {loading} = this.state;
11✔
456

457
    return (
11✔
458
      <div>
459
        <SubmissionsActionBox
460
          ref={r => (this.actionBox = r)}
22✔
461
          disabled={this.props.selection.length === 0}
462
          can_collect={this.props.can_collect}
463
          assignment_id={this.props.assignment_id}
464
          can_run_tests={this.props.can_run_tests}
465
          collectSubmissions={() => {
466
            this.setState({showCollectSubmissionsModal: true});
×
467
          }}
468
          printGroupingFiles={() => this.prepareGroupingFiles(true)}
×
469
          downloadGroupingFiles={() => this.prepareGroupingFiles(false)}
×
470
          showReleaseUrls={() => this.setState({showReleaseUrlsModal: true})}
×
471
          selection={this.props.selection}
472
          runTests={this.runTests}
473
          releaseMarks={() => this.toggleRelease(true)}
×
474
          unreleaseMarks={() => this.toggleRelease(false)}
×
475
          completeResults={() => this.setMarkingStates("complete")}
×
476
          incompleteResults={() => this.setMarkingStates("incomplete")}
×
477
          authenticity_token={this.props.authenticity_token}
478
          release_with_urls={this.props.release_with_urls}
479
          inactiveGroupsCount={this.state.inactiveGroupsCount}
480
          updateShowInactiveGroups={this.toggleShowInactiveGroups}
481
        />
482
        <CheckboxTable
483
          ref={r => (this.checkboxTable = r)}
22✔
484
          data={this.state.groupings}
485
          columns={this.columns()}
486
          defaultSorted={[
487
            {
488
              id: "group_name",
489
            },
490
          ]}
491
          filterable
492
          defaultFiltered={this.props.defaultFiltered}
493
          filtered={this.state.filtered}
494
          onFilteredChange={this.onFilteredChange}
495
          loading={loading}
496
          getTrProps={this.getTrProps}
497
          {...this.props.getCheckboxProps()}
498
        />
499
        <CollectSubmissionsModal
500
          isOpen={this.state.showCollectSubmissionsModal}
501
          isScannedExam={this.props.is_scanned_exam}
502
          onRequestClose={() => {
503
            this.setState({showCollectSubmissionsModal: false});
×
504
          }}
505
          onSubmit={this.collectSubmissions}
506
        />
507
        <ReleaseUrlsModal
508
          isOpen={this.state.showReleaseUrlsModal}
509
          data={this.state.groupings.filter(
510
            g => this.props.selection.includes(g._id) && !!g.result_view_token
14!
511
          )}
512
          groupNameWithMembers={this.groupNameWithMembers}
513
          groupNameFilter={this.groupNameFilter}
514
          course_id={this.props.course_id}
515
          assignment_id={this.props.assignment_id}
516
          refreshViewTokens={this.refreshViewTokens}
517
          refreshViewTokenExpiry={this.refreshViewTokenExpiry}
518
          onRequestClose={() => {
519
            this.setState({showReleaseUrlsModal: false});
×
520
          }}
521
        />
522
      </div>
523
    );
524
  }
525
}
526

527
let SubmissionTable = withSelection(RawSubmissionTable);
1✔
528
SubmissionTable.defaultProps = {
1✔
529
  can_collect: false,
530
  is_timed: false,
531
  can_run_tests: false,
532
};
533

534
class SubmissionsActionBox extends React.Component {
535
  constructor(props) {
536
    super(props);
4✔
537
    this.state = {
4✔
538
      button_disabled: false,
539
    };
540
  }
541

542
  render = () => {
4✔
543
    let displayInactiveGroupsCheckbox,
544
      completeButton,
545
      incompleteButton,
546
      collectButton,
547
      runTestsButton,
548
      releaseMarksButton,
549
      unreleaseMarksButton,
550
      showReleaseUrlsButton;
551

552
    let displayInactiveGroupsTooltip = "";
11✔
553

554
    if (this.props.inactiveGroupsCount !== null) {
11!
555
      displayInactiveGroupsTooltip = `${I18n.t("activerecord.attributes.grouping.inactive_groups", {
11✔
556
        count: this.props.inactiveGroupsCount,
557
      })}`;
558
    }
559

560
    displayInactiveGroupsCheckbox = (
11✔
561
      <>
562
        <input
563
          id="show_inactive_groups"
564
          name="show_inactive_groups"
565
          type="checkbox"
566
          checked={this.props.showInactiveGroups}
567
          onChange={e => this.props.updateShowInactiveGroups(e.target.checked)}
3✔
568
          className={"hide-user-checkbox"}
569
          data-testid={"show_inactive_groups"}
570
        />
571
        <label
572
          title={displayInactiveGroupsTooltip}
573
          htmlFor="show_inactive_groups"
574
          data-testid={"show_inactive_groups_tooltip"}
575
        >
576
          {I18n.t("submissions.groups.display_inactive")}
577
        </label>
578
      </>
579
    );
580

581
    completeButton = (
11✔
582
      <button
583
        onClick={this.props.completeResults}
584
        disabled={this.props.disabled}
585
        title={I18n.t("results.set_to_complete")}
586
      >
587
        <FontAwesomeIcon icon="fa-solid fa-circle-check" />
588
        <span className="button-text">{I18n.t("results.set_to_complete")}</span>
589
      </button>
590
    );
591

592
    incompleteButton = (
11✔
593
      <button
594
        onClick={this.props.incompleteResults}
595
        disabled={this.props.disabled}
596
        title={I18n.t("results.set_to_incomplete")}
597
      >
598
        <FontAwesomeIcon icon="fa-solid fa-pen" />
599
        <span className="button-text">{I18n.t("results.set_to_incomplete")}</span>
600
      </button>
601
    );
602
    if (this.props.can_collect) {
11!
603
      collectButton = (
11✔
604
        <button
605
          onClick={this.props.collectSubmissions}
606
          disabled={this.props.disabled}
607
          title={I18n.t("submissions.collect.submit")}
608
        >
609
          <FontAwesomeIcon icon="fa-solid fa-file-import" />
610
          <span className="button-text">{I18n.t("submissions.collect.submit")}</span>
611
        </button>
612
      );
613

614
      releaseMarksButton = (
11✔
615
        <button
616
          disabled={this.props.disabled}
617
          onClick={this.props.releaseMarks}
618
          title={I18n.t("submissions.release_marks")}
619
        >
620
          <FontAwesomeIcon icon="fa-solid fa-envelope-circle-check" />
621
          <span className="button-text">{I18n.t("submissions.release_marks")}</span>
622
        </button>
623
      );
624
      unreleaseMarksButton = (
11✔
625
        <button
626
          disabled={this.props.disabled}
627
          onClick={this.props.unreleaseMarks}
628
          title={I18n.t("submissions.unrelease_marks")}
629
        >
630
          <span className="fa-layers fa-fw">
631
            <FontAwesomeIcon
632
              icon="fa-solid fa-envelope-circle-check"
633
              color={document.documentElement.style.getPropertyValue("--disabled_text")}
634
            />
635
            <FontAwesomeIcon icon="fa-solid fa-slash" />
636
          </span>
637
          <span className="button-text">{I18n.t("submissions.unrelease_marks")}</span>
638
        </button>
639
      );
640
      if (this.props.release_with_urls) {
11!
641
        showReleaseUrlsButton = (
×
642
          <button
643
            onClick={this.props.showReleaseUrls}
644
            disabled={this.props.disabled}
645
            title={I18n.t("submissions.show_release_tokens")}
646
          >
647
            <FontAwesomeIcon icon="fa-solid fa-link" />
648
            <span className="button-text">{I18n.t("submissions.show_release_tokens")}</span>
649
          </button>
650
        );
651
      }
652
    }
653
    if (this.props.can_run_tests) {
11!
654
      runTestsButton = (
×
655
        <button
656
          onClick={this.props.runTests}
657
          disabled={this.props.disabled}
658
          title={I18n.t("submissions.run_tests")}
659
        >
660
          <FontAwesomeIcon icon="fa-solid fa-rocket" />
661
          <span className="button-text">{I18n.t("submissions.run_tests")}</span>
662
        </button>
663
      );
664
    }
665

666
    let downloadGroupingFilesButton = (
667
      <button
11✔
668
        onClick={this.props.downloadGroupingFiles}
669
        disabled={this.props.disabled}
670
        title={I18n.t("download_the", {item: I18n.t("activerecord.models.submission.other")})}
671
      >
672
        <FontAwesomeIcon icon="fa-solid fa-download" />
673
        <span className="button-text">
674
          {I18n.t("download_the", {item: I18n.t("activerecord.models.submission.other")})}
675
        </span>
676
      </button>
677
    );
678

679
    let printGroupingFilesButton = (
680
      <button
11✔
681
        onClick={this.props.printGroupingFiles}
682
        disabled={this.props.disabled}
683
        title={I18n.t("print_the", {item: I18n.t("activerecord.models.submission.other")})}
684
      >
685
        <FontAwesomeIcon icon="fa-solid fa-print" />
686
        <span className="button-text">
687
          {I18n.t("print_the", {item: I18n.t("activerecord.models.submission.other")})}
688
        </span>
689
      </button>
690
    );
691

692
    return (
11✔
693
      <div className="rt-action-box">
694
        {displayInactiveGroupsCheckbox}
695
        {completeButton}
696
        {incompleteButton}
697
        {collectButton}
698
        {downloadGroupingFilesButton}
699
        {printGroupingFilesButton}
700
        {runTestsButton}
701
        {releaseMarksButton}
702
        {unreleaseMarksButton}
703
        {showReleaseUrlsButton}
704
      </div>
705
    );
706
  };
707
}
708

709
export function makeSubmissionTable(elem, props) {
NEW
710
  const root = createRoot(elem);
×
NEW
711
  return root.render(<SubmissionTable {...props} />);
×
712
}
713

714
function generateMessage(status_data) {
UNCOV
715
  let message_data = {};
×
UNCOV
716
  switch (status_data["status"]) {
×
717
    case "failed":
718
      if (!status_data["exception"] || !status_data["exception"]["message"]) {
×
UNCOV
719
        message_data["error"] = I18n.t("job.status.failed.no_message");
×
720
      } else {
721
        message_data["error"] = I18n.t("job.status.failed.message", {
×
722
          error: status_data["exception"]["message"],
723
        });
724
      }
UNCOV
725
      break;
×
726
    case "completed":
727
      if (status_data["job_class"] === "AutotestRunJob") {
×
UNCOV
728
        message_data["success"] = I18n.t("automated_tests.autotest_run_job.status.completed");
×
729
      } else if (status_data["job_class"] === "AutotestResultsJob") {
×
730
        message_data["success"] = I18n.t("automated_tests.autotest_results_job.status.completed");
×
731
      } else {
732
        message_data["success"] = I18n.t("job.status.completed");
×
733
      }
734
      break;
×
735
    case "queued":
736
      message_data["notice"] = I18n.t("job.status.queued");
×
UNCOV
737
      break;
×
738
    default:
739
      if (status_data["job_class"] === "SubmissionsJob") {
×
UNCOV
740
        let progress = status_data["progress"];
×
741
        let total = status_data["total"];
×
742
        message_data["notice"] = I18n.t("submissions.collect.status.in_progress", {
×
743
          progress,
744
          total,
745
        });
746
      } else {
UNCOV
747
        message_data["notice"] = I18n.t("job.status.in_progress", {progress, total});
×
748
      }
749
  }
UNCOV
750
  if (status_data["warning_message"]) {
×
UNCOV
751
    message_data["warning"] = status_data["warning_message"];
×
752
  }
753
  return message_data;
×
754
}
755

756
export {SubmissionTable};
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