• 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

45.74
/app/javascript/Components/test_run_table.jsx
1
import React from "react";
2
import {createRoot} from "react-dom/client";
3
import ReactTable from "react-table";
4
import {dateSort} from "./Helpers/table_helpers";
5
import consumer from "../channels/consumer";
6
import {renderFlashMessages} from "../common/flash";
7
import {TestGroupResultTable} from "./test_group_result_table";
8

9
export class TestRunTable extends React.Component {
10
  constructor(props) {
11
    super(props);
12✔
12
    this.state = {
11✔
13
      data: [],
14
      loading: true,
15
      expanded: {},
16
    };
17
    this.testRuns = React.createRef();
11✔
18
  }
19

20
  componentDidMount() {
21
    this.fetchData();
11✔
22
    this.create_test_runs_channel_subscription();
11✔
23
  }
24

25
  componentDidUpdate(prevProps) {
26
    if (
11!
27
      prevProps.result_id !== this.props.result_id ||
33✔
28
      prevProps.instructor_run !== this.props.instructor_run ||
29
      prevProps.instructor_view !== this.props.instructor_view
30
    ) {
UNCOV
31
      this.setState({loading: true}, this.fetchData);
×
32
    }
33
  }
34

35
  fetchData = () => {
11✔
36
    let fetchDetails = {
11✔
37
      headers: {
38
        Accept: "application/json",
39
      },
40
    };
41
    let url;
42
    if (this.props.instructor_run) {
11!
43
      if (this.props.instructor_view) {
11!
44
        url = Routes.get_test_runs_instructors_course_result_path(
11✔
45
          this.props.course_id,
46
          this.props.result_id
47
        );
48
      } else {
UNCOV
49
        url = Routes.get_test_runs_instructors_released_course_result_path(
×
50
          this.props.course_id,
51
          this.props.result_id
52
        );
53
      }
54
    } else {
UNCOV
55
      url = Routes.get_test_runs_students_course_assignment_automated_tests_path(
×
56
        this.props.course_id,
57
        this.props.assignment_id
58
      );
59
    }
60
    fetch(url, fetchDetails)
11✔
61
      .then(response => {
62
        if (response.ok) {
11!
63
          return response.json();
11✔
64
        }
65
      })
66
      .then(data => {
67
        this.setState({
11✔
68
          data: data,
69
          loading: false,
70
          expanded: data.length > 0 ? {0: true} : {},
11✔
71
        });
72
      });
73
  };
74

75
  onExpandedChange = newExpanded => this.setState({expanded: newExpanded});
11✔
76

77
  create_test_runs_channel_subscription = () => {
11✔
78
    consumer.subscriptions.create(
11✔
79
      {
80
        channel: "TestRunsChannel",
81
        course_id: this.props.course_id,
82
        assignment_id: this.props.assignment_id,
83
        grouping_id: this.props.grouping_id,
84
        submission_id: this.props.submission_id,
85
      },
86
      {
87
        connected: () => {},
88
        disconnected: () => {},
89
        received: data => {
90
          // Called when there's incoming data on the websocket for this channel
91
          if (data["status"] !== null) {
×
92
            let message_data = generateMessage(data);
×
UNCOV
93
            renderFlashMessages(message_data);
×
94
          }
UNCOV
95
          if (data["status"] === "completed") {
×
96
            // Note: this gets called after AutotestRunJob completes (when a new
97
            // TestRun is created), and after an AutotestResultsJob completed
98
            // (when test results are available).
UNCOV
99
            this.fetchData();
×
100
          }
101
        },
102
      }
103
    );
104
  };
105

106
  render() {
107
    let height;
108
    if (this.props.instructor_view) {
22!
109
      // 3.5em is the vertical space for the action bar (and run tests button)
110
      height = "calc(599px - 3.5em)";
22✔
111
    } else {
UNCOV
112
      height = "599px";
×
113
    }
114

115
    return (
22✔
116
      <div>
117
        <ReactTable
118
          ref={this.testRuns}
119
          data={this.state.data}
120
          key={this.state.data.length ? this.state.data[0]["test_runs.id"] : "empty-table"}
22✔
121
          columns={[
122
            {
123
              id: "created_at",
124
              accessor: row => row["test_runs.created_at"],
10✔
125
              sortMethod: dateSort,
126
              minWidth: 300,
127
            },
128
            {
129
              id: "user_name",
130
              accessor: row => row["users.user_name"],
10✔
131
              Cell: ({value}) => I18n.t("activerecord.attributes.test_run.user") + " " + value,
10✔
132
              show: !this.props.instructor_run || this.props.instructor_view,
44✔
133
              width: 120,
134
            },
135
            {
136
              id: "status",
137
              accessor: row =>
138
                I18n.t(`automated_tests.test_runs_statuses.${row["test_runs.status"]}`),
10✔
139
              width: 120,
140
            },
141
          ]}
142
          SubComponent={row =>
143
            row.original["test_runs.problems"] ? (
10✔
144
              <pre>{row.original["test_runs.problems"]}</pre>
145
            ) : (
146
              <TestGroupResultTable
147
                key={row.original.id_}
148
                data={row.original["test_results"]}
149
                course_id={this.props.course_id}
150
              />
151
            )
152
          }
153
          noDataText={I18n.t("automated_tests.no_results")}
154
          getTheadProps={() => {
155
            return {
23✔
156
              style: {display: "none"},
157
            };
158
          }}
159
          defaultSorted={[{id: "created_at", desc: true}]}
160
          expanded={this.state.expanded}
161
          onExpandedChange={this.onExpandedChange}
162
          loading={this.state.loading}
163
          style={{maxHeight: height}}
164
        />
165
      </div>
166
    );
167
  }
168
}
169

170
export function makeTestRunTable(elem, props) {
171
  const root = createRoot(elem);
×
UNCOV
172
  root.render(<TestRunTable {...props} />);
×
173
}
174

175
function generateMessage(status_data) {
UNCOV
176
  let message_data = {};
×
UNCOV
177
  switch (status_data["status"]) {
×
178
    case "failed":
UNCOV
179
      if (!status_data["exception"] || !status_data["exception"]["message"]) {
×
UNCOV
180
        message_data["error"] = I18n.t("job.status.failed.no_message");
×
181
      } else {
UNCOV
182
        message_data["error"] = I18n.t("job.status.failed.message", {
×
183
          error: status_data["exception"]["message"],
184
        });
185
      }
UNCOV
186
      break;
×
187
    case "completed":
UNCOV
188
      if (status_data["job_class"] === "AutotestRunJob") {
×
UNCOV
189
        message_data["success"] = I18n.t("automated_tests.autotest_run_job.status.completed");
×
190
      } else {
UNCOV
191
        message_data["success"] = I18n.t("automated_tests.autotest_results_job.status.completed");
×
192
      }
UNCOV
193
      break;
×
194
    case "queued":
UNCOV
195
      message_data["notice"] = I18n.t("job.status.queued");
×
UNCOV
196
      break;
×
197
    case "service_unavailable":
UNCOV
198
      message_data["notice"] = status_data["exception"]["message"];
×
UNCOV
199
      break;
×
200
    default:
UNCOV
201
      message_data["notice"] = I18n.t("automated_tests.autotest_run_job.status.in_progress");
×
202
  }
UNCOV
203
  if (status_data["warning_message"]) {
×
UNCOV
204
    message_data["warning"] = status_data["warning_message"];
×
205
  }
UNCOV
206
  return message_data;
×
207
}
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