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

MarkUsProject / Markus / 20143075828

11 Dec 2025 06:18PM UTC coverage: 91.513%. Remained the same
20143075828

Pull #7763

github

web-flow
Merge 9f55e660a into 3421ef3b2
Pull Request #7763: Release 2.9.0

914 of 1805 branches covered (50.64%)

Branch coverage included in aggregate %.

1584 of 1666 new or added lines in 108 files covered. (95.08%)

573 existing lines in 35 files now uncovered.

43650 of 46892 relevant lines covered (93.09%)

121.63 hits per line

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

59.2
/app/javascript/Components/Result/marks_panel.jsx
1
import React from "react";
2

3
import CheckboxCriterionInput from "./checkbox_criterion_input";
4
import FlexibleCriterionInput from "./flexible_criterion_input";
5
import RubricCriterionInput from "./rubric_criterion_input";
6

7
export class MarksPanel extends React.Component {
8
  static defaultProps = {
1✔
9
    marks: [],
10
  };
11

12
  constructor(props) {
13
    super(props);
8✔
14

15
    // Set first criterion active by default
16
    const hasMarks = Array.isArray(props.marks) && props.marks.length > 0;
7✔
17
    const first = !props.released_to_students && hasMarks ? props.marks[0] : null;
7✔
18

19
    this.state = {
7✔
20
      expanded: new Set(),
21
      activeCriterionId: first ? first.id : null,
7✔
22
    };
23
  }
24

25
  setActiveCriterionId = id => {
7✔
26
    this.setState({activeCriterionId: id});
3✔
27
  };
28

29
  nextCriterion = () => {
7✔
30
    const criteria = this.props.marks.filter(
1✔
31
      c => this.props.assigned_criteria === null || this.props.assigned_criteria.includes(c.id)
2!
32
    ); // skip unassigned
33

34
    // If criteria empty, set active criterion to null
35
    if (criteria.length === 0) {
1!
NEW
36
      this.setActiveCriterionId(null);
×
NEW
37
      return;
×
38
    }
39

40
    const idx = criteria.findIndex(c => c.id === this.state.activeCriterionId);
1✔
41
    const next = idx === -1 || idx === criteria.length - 1 ? criteria[0] : criteria[idx + 1];
1!
42
    this.setActiveCriterionId(next.id);
1✔
43
  };
44

45
  prevCriterion = () => {
7✔
46
    const criteria = this.props.marks.filter(
1✔
47
      c => this.props.assigned_criteria === null || this.props.assigned_criteria.includes(c.id)
2!
48
    );
49

50
    // If criteria empty, set active criterion to null
51
    if (criteria.length === 0) {
1!
NEW
52
      this.setActiveCriterionId(null);
×
NEW
53
      return;
×
54
    }
55

56
    const idx = criteria.findIndex(c => c.id === this.state.activeCriterionId);
1✔
57
    const prev = idx <= 0 ? criteria[criteria.length - 1] : criteria[idx - 1];
1!
58
    this.setActiveCriterionId(prev.id);
1✔
59
  };
60

61
  componentDidMount() {
62
    if (!this.props.released_to_students) {
7!
63
      // Expose the whole component for keyboard shortcuts
64
      window.marksPanel = this;
7✔
65
    }
66
  }
67

68
  componentDidUpdate(prevProps) {
69
    if (prevProps.marks !== this.props.marks) {
11!
70
      // Expand by default if a mark has not yet been given, and the current user can give the mark.
71
      let expanded = new Set();
×
UNCOV
72
      this.props.marks.forEach(data => {
×
UNCOV
73
        const key = data.id;
×
UNCOV
74
        if (
×
75
          (data.mark === null || data.mark === undefined) &&
×
76
          (this.props.assigned_criteria === null || this.props.assigned_criteria.includes(key))
77
        ) {
UNCOV
78
          expanded.add(key);
×
79
        }
80
      });
UNCOV
81
      this.setState({expanded});
×
82
    }
83
  }
84

85
  expandAll = onlyUnmarked => {
7✔
86
    let expanded = new Set();
×
87
    this.props.marks.forEach(markData => {
×
UNCOV
88
      if (!onlyUnmarked || markData.mark === null || markData.mark === undefined) {
×
UNCOV
89
        expanded.add(markData.id);
×
90
      }
91
    });
UNCOV
92
    this.setState({expanded});
×
93
  };
94

95
  collapseAll = () => {
7✔
UNCOV
96
    this.setState({expanded: new Set()});
×
97
  };
98

99
  toggleExpanded = key => {
7✔
100
    if (this.state.expanded.has(key)) {
8!
UNCOV
101
      this.state.expanded.delete(key);
×
102
    } else {
103
      this.state.expanded.add(key);
8✔
104
    }
105
    this.setState({expanded: this.state.expanded});
8✔
106
  };
107

108
  updateMark = (criterion_id, mark) => {
7✔
UNCOV
109
    let result = this.props.updateMark(criterion_id, mark);
×
UNCOV
110
    if (result !== undefined) {
×
UNCOV
111
      result.then(() => {
×
112
        this.state.expanded.delete(criterion_id);
×
UNCOV
113
        this.setState({expanded: this.state.expanded});
×
114
      });
115
    }
116
  };
117

118
  destroyMark = (e, criterion_id) => {
7✔
UNCOV
119
    e.stopPropagation();
×
120
    this.props.destroyMark(criterion_id);
×
121
  };
122

123
  renderMarkComponent = markData => {
7✔
124
    const key = markData.id;
34✔
125
    const unassigned =
126
      this.props.assigned_criteria !== null && !this.props.assigned_criteria.includes(key);
34✔
127

128
    const props = {
34✔
129
      active: this.state.activeCriterionId === key,
130
      key: key,
131
      released_to_students: this.props.released_to_students,
132
      unassigned: unassigned,
133
      updateMark: this.updateMark,
134
      destroyMark: this.destroyMark,
135
      expanded: this.state.expanded.has(key),
136
      oldMark: this.props.old_marks[markData.id],
137
      setActive: () => this.setActiveCriterionId(key),
1✔
138
      toggleExpanded: () => this.toggleExpanded(key),
8✔
139
      annotations: this.props.annotations,
140
      revertToAutomaticDeductions: this.props.revertToAutomaticDeductions,
141
      findDeductiveAnnotation: this.props.findDeductiveAnnotation,
142
      ...markData,
143
    };
144
    if (markData.criterion_type === "CheckboxCriterion") {
34✔
145
      return <CheckboxCriterionInput {...props} />;
16✔
146
    } else if (markData.criterion_type === "FlexibleCriterion") {
18✔
147
      return <FlexibleCriterionInput {...props} />;
16✔
148
    } else if (markData.criterion_type === "RubricCriterion") {
2!
149
      return <RubricCriterionInput {...props} />;
2✔
150
    } else {
UNCOV
151
      return null;
×
152
    }
153
  };
154

155
  render() {
156
    const markComponents = this.props.marks.map(this.renderMarkComponent);
18✔
157

158
    return (
18✔
159
      <div id="mark_viewer" className="flex-col">
160
        {!this.props.released_to_students && (
36✔
161
          <div className="text-center">
UNCOV
162
            <button className="inline-button" onClick={() => this.expandAll()}>
×
163
              {I18n.t("results.expand_all")}
164
            </button>
UNCOV
165
            <button className="inline-button" onClick={() => this.expandAll(true)}>
×
166
              {I18n.t("results.expand_unmarked")}
167
            </button>
UNCOV
168
            <button className="inline-button" onClick={() => this.collapseAll()}>
×
169
              {I18n.t("results.collapse_all")}
170
            </button>
171
          </div>
172
        )}
173
        <div id="mark_criteria">
174
          <ul className="marks-list">{markComponents}</ul>
175
        </div>
176
      </div>
177
    );
178
  }
179
}
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