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

MarkUsProject / Markus / 17254526227

27 Aug 2025 01:12AM UTC coverage: 91.814% (-0.03%) from 91.846%
17254526227

Pull #7602

github

web-flow
Merge 635cdd9fa into 3d2b5b1d1
Pull Request #7602: Added loading icon for instructor table

694 of 1476 branches covered (47.02%)

Branch coverage included in aggregate %.

11 of 12 new or added lines in 5 files covered. (91.67%)

31 existing lines in 2 files now uncovered.

42142 of 45179 relevant lines covered (93.28%)

118.8 hits per line

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

23.49
/app/javascript/Components/Helpers/table_helpers.jsx
1
import React from "react";
2
import {Grid} from "react-loader-spinner";
3

4
/**
5
 * @file
6
 * Provides generic helper functions and components for react-table tables.
7
 */
8

9
export function customLoadingProp(props) {
10
  const {loading} = props;
96✔
11

12
  if (loading) {
96✔
13
    return (
34✔
14
      <div className="loading-spinner" data-testid="loading-spinner">
15
        <Grid
16
          visible={true}
17
          height="25"
18
          width="25"
19
          color="#31649B"
20
          aria-label="grid-loading"
21
          radius="12.5"
22
          wrapperStyle={{}}
23
          wrapperClass="grid-wrapper"
24
        />
25
      </div>
26
    );
27
  }
28

29
  return null;
62✔
30
}
31

32
export function defaultSort(a, b) {
33
  // Sort values, putting undefined/nulls below all other values.
34
  // Based on react-table v6 defaultSortMethod (https://github.com/tannerlinsley/react-table/tree/v6/),
35
  // but not string-based.
36
  if ((a === undefined || a === null) && (b === undefined || b === null)) {
×
37
    return 0;
×
38
  } else if (a === undefined || a === null) {
×
39
    return -1;
×
UNCOV
40
  } else if (b === undefined || b === null) {
×
UNCOV
41
    return 1;
×
42
  } else {
43
    // force any string values to lowercase
UNCOV
44
    a = typeof a === "string" ? a.toLowerCase() : a;
×
45
    b = typeof b === "string" ? b.toLowerCase() : b;
×
46
    // Return either 1 or -1 to indicate a sort priority
UNCOV
47
    if (a > b) {
×
48
      return 1;
×
49
    }
UNCOV
50
    if (a < b) {
×
UNCOV
51
      return -1;
×
52
    }
53
    // returning 0, undefined or any falsey value will use subsequent sorts or
54
    // the index as a tiebreaker
UNCOV
55
    return 0;
×
56
  }
57
}
58

59
/**
60
 * Case insensitive, locale aware, string filter function
61
 */
62
export function stringFilterMethod(filter, row) {
UNCOV
63
  return String(row[filter.id])
×
64
    .toLocaleLowerCase()
65
    .includes(String(filter.value).toLocaleLowerCase());
66
}
67

68
export function dateSort(a, b) {
69
  /** Sort values as dates */
70
  if (!a && !b) {
×
71
    return 0;
×
72
  } else if (!a) {
×
73
    return -1;
×
UNCOV
74
  } else if (!b) {
×
75
    return 1;
×
76
  } else {
77
    let a_date = new Date(a);
×
UNCOV
78
    let b_date = new Date(b);
×
UNCOV
79
    return (a_date || 0) - (b_date || 0);
×
80
  }
81
}
82

83
export function durationSort(a, b) {
84
  /** Sort values as a duration in weeks, days, hours, etc. */
85
  a = [a.weeks || -1, a.days || -1, a.hours || -1, a.minutes || -1, a.seconds || -1];
×
86
  b = [b.weeks || -1, b.days || -1, b.hours || -1, b.minutes || -1, b.seconds || -1];
×
87
  if (a < b) {
×
88
    return 1;
×
UNCOV
89
  } else if (b < a) {
×
90
    return -1;
×
91
  } else {
UNCOV
92
    return 0;
×
93
  }
94
}
95

96
/**
97
 * Text-based search filter. Based on react-table's default search filter,
98
 * with an additional aria-label attribute.
99
 */
100
export function textFilter({filter, onChange, column}) {
UNCOV
101
  return (
×
102
    <input
103
      type="text"
104
      style={{
105
        width: "100%",
106
      }}
107
      placeholder={column.Placeholder}
108
      value={filter ? filter.value : ""}
×
109
      aria-label={`${I18n.t("search")} ${column.Header || ""}`}
×
UNCOV
110
      onChange={event => onChange(event.target.value)}
×
111
    />
112
  );
113
}
114

115
/**
116
 * Select-based search filter. Options are generated from the custom column attribute
117
 * filterOptions, which is a list of objects with keys "value" and "text".
118
 * A default "all" option is prepended to the list of options; the text can be
119
 * customized by setting the filterAllOptionText column attribute.
120
 */
121
export function selectFilter({filter, onChange, column}) {
122
  let options = (column.filterOptions || []).map(({value, text}) => (
112!
123
    <option value={value} key={value}>
203✔
124
      {text}
125
    </option>
126
  ));
127
  let allOptionText = column.filterAllOptionText || I18n.t("all");
112✔
128
  options.unshift(
112✔
129
    <option value="all" key="all">
130
      {allOptionText}
131
    </option>
132
  );
133

134
  return (
112✔
135
    <select
UNCOV
136
      onChange={event => onChange(event.target.value)}
×
137
      style={{width: "100%"}}
138
      value={filter ? filter.value : "all"}
112!
139
      aria-label={I18n.t("filter_by", {name: column.Header})}
140
    >
141
      {options}
142
    </select>
143
  );
144
}
145

146
export function markingStateColumn(marking_states, markingStateFilter, ...override_keys) {
147
  return {
11✔
148
    Header: I18n.t("activerecord.attributes.result.marking_state"),
149
    accessor: "marking_state",
150
    Cell: row => {
151
      let marking_state = "";
13✔
152
      switch (row.original.marking_state) {
13!
153
        case "not_collected":
UNCOV
154
          marking_state = I18n.t("submissions.state.not_collected");
×
155
          break;
×
156
        case "incomplete":
UNCOV
157
          marking_state = I18n.t("submissions.state.in_progress");
×
158
          break;
×
159
        case "complete":
UNCOV
160
          marking_state = I18n.t("submissions.state.complete");
×
UNCOV
161
          break;
×
162
        case "released":
163
          marking_state = I18n.t("submissions.state.released");
13✔
164
          break;
13✔
165
        case "remark":
UNCOV
166
          marking_state = I18n.t("submissions.state.remark_requested");
×
167
          break;
×
168
        case "before_due_date":
UNCOV
169
          marking_state = I18n.t("submissions.state.before_due_date");
×
UNCOV
170
          break;
×
171
        default:
172
          // should not get here
UNCOV
173
          marking_state = row.original.marking_state;
×
174
      }
175
      return marking_state;
13✔
176
    },
177
    filterMethod: (filter, row) => {
UNCOV
178
      if (filter.value === "all") {
×
179
        return true;
×
180
      } else {
UNCOV
181
        return filter.value === row[filter.id];
×
182
      }
183
    },
184
    filterAllOptionText:
185
      I18n.t("all") +
186
      (markingStateFilter === "all"
11!
187
        ? ` (${Object.values(marking_states).reduce((a, b) => a + b)})`
55✔
188
        : ""),
189
    filterOptions: [
190
      {
191
        value: "before_due_date",
192
        text:
193
          I18n.t("submissions.state.before_due_date") +
194
          (["before_due_date", "all"].includes(markingStateFilter)
11!
195
            ? ` (${marking_states["before_due_date"]})`
196
            : ""),
197
      },
198
      {
199
        value: "not_collected",
200
        text:
201
          I18n.t("submissions.state.not_collected") +
202
          (["not_collected", "all"].includes(markingStateFilter)
11!
203
            ? ` (${marking_states["not_collected"]})`
204
            : ""),
205
      },
206
      {
207
        value: "incomplete",
208
        text:
209
          I18n.t("submissions.state.in_progress") +
210
          (["incomplete", "all"].includes(markingStateFilter)
11!
211
            ? ` (${marking_states["incomplete"]})`
212
            : ""),
213
      },
214
      {
215
        value: "complete",
216
        text:
217
          I18n.t("submissions.state.complete") +
218
          (["complete", "all"].includes(markingStateFilter)
11!
219
            ? ` (${marking_states["complete"]})`
220
            : ""),
221
      },
222
      {
223
        value: "released",
224
        text:
225
          I18n.t("submissions.state.released") +
226
          (["released", "all"].includes(markingStateFilter)
11!
227
            ? ` (${marking_states["released"]})`
228
            : ""),
229
      },
230
      {
231
        value: "remark",
232
        text:
233
          I18n.t("submissions.state.remark_requested") +
234
          (["remark", "all"].includes(markingStateFilter) ? ` (${marking_states["remark"]})` : ""),
11!
235
      },
236
    ],
237
    Filter: selectFilter,
238
    ...override_keys,
239
  };
240
}
241

242
export function getMarkingStates(data) {
243
  const markingStates = {
24✔
244
    not_collected: 0,
245
    incomplete: 0,
246
    complete: 0,
247
    released: 0,
248
    remark: 0,
249
    before_due_date: 0,
250
  };
251
  data.forEach(row => {
24✔
252
    markingStates[row["marking_state"]] += 1;
20✔
253
  });
254
  return markingStates;
24✔
255
}
256

257
export function customNoDataComponent({children, loading}) {
258
  if (loading) {
64✔
259
    return null;
22✔
260
  }
261
  return <p className="rt-no-data">{children}</p>;
42✔
262
}
263

264
export function customNoDataProps({state}) {
NEW
UNCOV
265
  return {loading: state.loading, data: state.data};
×
266
}
267

268
// export function customNoDataText(props) {
269
//   const {loading} = props;
270
//   if (loading) {
271
//     return "";
272
//   }
273
//   return I18n.t("students.empty_table");
274
// }
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