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

MarkUsProject / Markus / 13122420635

03 Feb 2025 08:39PM UTC coverage: 91.849% (-0.02%) from 91.865%
13122420635

Pull #7393

github

web-flow
Merge f9da04898 into 0597f5222
Pull Request #7393: update remote_autotest_settings_id validation

624 of 1361 branches covered (45.85%)

Branch coverage included in aggregate %.

23 of 23 new or added lines in 2 files covered. (100.0%)

85 existing lines in 11 files now uncovered.

41281 of 44263 relevant lines covered (93.26%)

120.26 hits per line

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

89.61
/app/javascript/Components/student_table.jsx
1
import React from "react";
2
import {createRoot} from "react-dom/client";
3
import PropTypes from "prop-types";
4

5
import {CheckboxTable, withSelection} from "./markus_with_selection_hoc";
6
import {selectFilter} from "./Helpers/table_helpers";
7

8
class RawStudentTable extends React.Component {
9
  constructor() {
10
    super();
18✔
11
    this.state = {
17✔
12
      data: {
13
        students: [],
14
        sections: {},
15
        counts: {all: 0, active: 0, inactive: 0},
16
      },
17
      loading: true,
18
    };
19
  }
20

21
  componentDidMount() {
22
    this.fetchData();
17✔
23
  }
24

25
  fetchData = () => {
17✔
26
    fetch(Routes.course_students_path(this.props.course_id), {
17✔
27
      headers: {
28
        Accept: "application/json",
29
      },
30
    })
31
      .then(response => {
32
        if (response.ok) {
17!
33
          return response.json();
17✔
34
        }
35
      })
36
      .then(res => {
37
        this.setState({
17✔
38
          data: res,
39
          loading: false,
40
          selection: [],
41
          selectAll: false,
42
        });
43
      });
44
  };
45

46
  /* Called when an action is run */
47
  onSubmit = event => {
17✔
48
    event.preventDefault();
1✔
49

50
    const data = {
1✔
51
      student_ids: this.props.selection,
52
      bulk_action: this.actionBox.state.action,
53
      grace_credits: this.actionBox.state.grace_credits,
54
      section: this.actionBox.state.section,
55
    };
56

57
    this.setState({loading: true});
1✔
58
    $.ajax({
1✔
59
      method: "patch",
60
      url: Routes.bulk_modify_course_students_path(this.props.course_id),
61
      data: data,
62
    }).then(this.fetchData);
63
  };
64

65
  render() {
66
    const {data, loading} = this.state;
21✔
67

68
    return (
21✔
69
      <div data-testid={"raw_student_table"}>
70
        <StudentsActionBox
71
          ref={r => (this.actionBox = r)}
42✔
72
          sections={data.sections}
73
          disabled={this.props.selection.length === 0}
74
          onSubmit={this.onSubmit}
75
          authenticity_token={this.props.authenticity_token}
76
        />
77
        <CheckboxTable
78
          ref={r => (this.checkboxTable = r)}
42✔
79
          data={data.students}
80
          columns={[
81
            {
82
              Header: I18n.t("activerecord.attributes.user.user_name"),
83
              accessor: "user_name",
84
              id: "user_name",
85
              minWidth: 120,
86
            },
87
            {
88
              Header: I18n.t("activerecord.attributes.user.first_name"),
89
              accessor: "first_name",
90
              minWidth: 120,
91
            },
92
            {
93
              Header: I18n.t("activerecord.attributes.user.last_name"),
94
              accessor: "last_name",
95
              minWidth: 120,
96
            },
97
            {
98
              Header: I18n.t("activerecord.attributes.user.email"),
99
              accessor: "email",
100
              minWidth: 150,
101
            },
102
            {
103
              Header: I18n.t("activerecord.attributes.user.id_number"),
104
              accessor: "id_number",
105
              minWidth: 90,
106
              className: "number",
107
            },
108
            {
109
              Header: I18n.t("activerecord.models.section", {count: 1}),
110
              accessor: "section",
111
              id: "section",
112
              Cell: ({value}) => {
113
                return data.sections[value] || "";
2✔
114
              },
115
              show: Boolean(data.sections),
116
              filterMethod: (filter, row) => {
117
                if (filter.value === "all") {
3✔
118
                  return true;
1✔
119
                } else {
120
                  return data.sections[row[filter.id]] === filter.value;
2✔
121
                }
122
              },
123
              Filter: selectFilter,
124
              filterOptions: Object.entries(data.sections).map(kv => ({
1✔
125
                value: kv[1],
126
                text: kv[1],
127
              })),
128
            },
129
            {
130
              Header: I18n.t("activerecord.attributes.user.grace_credits"),
131
              id: "grace_credits",
132
              accessor: "remaining_grace_credits",
133
              className: "number",
134
              Cell: row => `${row.value} / ${row.original.grace_credits}`,
2✔
135
              minWidth: 90,
136
              Filter: ({filter, onChange}) => (
137
                <input
25✔
138
                  onChange={event => onChange(event.target.valueAsNumber)}
×
139
                  type="number"
140
                  min={0}
141
                  value={filter ? filter.value : ""}
25!
142
                />
143
              ),
144
              filterMethod: (filter, row) => {
145
                return (
3✔
146
                  isNaN(filter.value) || filter.value === row._original.remaining_grace_credits
5✔
147
                );
148
              },
149
            },
150
            {
151
              Header: I18n.t("roles.active") + "?",
152
              accessor: "hidden",
153
              Cell: ({value}) => (value ? I18n.t("roles.inactive") : I18n.t("roles.active")),
2!
154
              filterMethod: (filter, row) => {
155
                if (filter.value === "all") {
5✔
156
                  return true;
1✔
157
                } else {
158
                  return (
4✔
159
                    (filter.value === "active" && !row[filter.id]) ||
11✔
160
                    (filter.value === "inactive" && row[filter.id])
161
                  );
162
                }
163
              },
164
              Filter: selectFilter,
165
              filterOptions: [
166
                {
167
                  value: "active",
168
                  text: `${I18n.t("roles.active")} (${this.state.data.counts.active})`,
169
                },
170
                {
171
                  value: "inactive",
172
                  text: `${I18n.t("roles.inactive")} (${this.state.data.counts.inactive})`,
173
                },
174
              ],
175
              filterAllOptionText: `${I18n.t("all")} (${this.state.data.counts.all})`,
176
            },
177
            {
178
              Header: I18n.t("actions"),
179
              accessor: "_id",
180
              Cell: data => (
181
                <span>
2✔
182
                  <a href={Routes.edit_course_student_path(this.props.course_id, data.value)}>
183
                    {I18n.t("edit")}
184
                  </a>
185
                  &nbsp;
186
                </span>
187
              ),
188
              sortable: false,
189
              filterable: false,
190
            },
191
          ]}
192
          defaultSorted={[
193
            {
194
              id: "user_name",
195
            },
196
          ]}
197
          filterable
198
          loading={loading}
199
          {...this.props.getCheckboxProps()}
200
        />
201
      </div>
202
    );
203
  }
204
}
205

206
class StudentsActionBox extends React.Component {
207
  constructor() {
208
    super();
24✔
209
    this.state = {
24✔
210
      action: "give_grace_credits",
211
      grace_credits: 0,
212
      selected_section: "",
213
      button_disabled: false,
214
    };
215
  }
216

217
  inputChanged = event => {
24✔
218
    this.setState({[event.target.name]: event.target.value});
×
219
  };
220

221
  actionChanged = event => {
24✔
222
    this.setState({action: event.target.value});
3✔
223
  };
224

225
  render = () => {
24✔
226
    let optionalInputBox = null;
31✔
227
    if (this.state.action === "give_grace_credits") {
31✔
228
      optionalInputBox = (
29✔
229
        <input
230
          type="number"
231
          name="grace_credits"
232
          value={this.state.grace_credits}
233
          onChange={this.inputChanged}
234
        />
235
      );
236
    } else if (this.state.action === "update_section") {
2!
237
      if (Object.keys(this.props.sections).length > 0) {
2✔
238
        const section_options = Object.entries(this.props.sections).map(section => (
1✔
239
          <option key={section[0]} value={section[0]}>
2✔
240
            {section[1]}
241
          </option>
242
        ));
243
        optionalInputBox = (
1✔
244
          <select
245
            name="section"
246
            value={this.state.section}
247
            onChange={this.inputChanged}
248
            data-testid={"student_action_box_update_section"}
249
          >
250
            <option key={"none"} value={""}>
251
              {I18n.t("students.instructor_actions.no_section")}
252
            </option>
253
            {section_options}
254
          </select>
255
        );
256
      } else {
257
        optionalInputBox = <span>{I18n.t("sections.none")}</span>;
1✔
258
      }
259
    }
260

261
    return (
31✔
262
      <form onSubmit={this.props.onSubmit} data-testid={"student_action_box"}>
263
        <select
264
          value={this.state.action}
265
          onChange={this.actionChanged}
266
          data-testid={"student_action_box_select"}
267
        >
268
          <option value="give_grace_credits">
269
            {I18n.t("students.instructor_actions.give_grace_credits")}
270
          </option>
271
          <option value="update_section">
272
            {I18n.t("students.instructor_actions.update_section")}
273
          </option>
274
          <option value="hide">{I18n.t("students.instructor_actions.mark_inactive")}</option>
275
          <option value="unhide">{I18n.t("students.instructor_actions.mark_active")}</option>
276
        </select>
277
        {optionalInputBox}
278
        <input type="submit" disabled={this.props.disabled} value={I18n.t("apply")} />
279
        <input type="hidden" name="authenticity_token" value={this.props.authenticity_token} />
280
      </form>
281
    );
282
  };
283
}
284
StudentsActionBox.propTypes = {
2✔
285
  onSubmit: PropTypes.func,
286
  disabled: PropTypes.bool,
287
  authenticity_token: PropTypes.string,
288
  sections: PropTypes.object,
289
};
290

291
RawStudentTable.propTypes = {
2✔
292
  course_id: PropTypes.number,
293
  selection: PropTypes.array.isRequired,
294
  authenticity_token: PropTypes.string,
295
  getCheckboxProps: PropTypes.func.isRequired,
296
};
297

298
let StudentTable = withSelection(RawStudentTable);
2✔
299
function makeStudentTable(elem, props) {
300
  const root = createRoot(elem);
×
UNCOV
301
  root.render(<StudentTable {...props} />);
×
302
}
303
export {StudentTable, StudentsActionBox, makeStudentTable};
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