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

MarkUsProject / Markus / 18383183058

09 Oct 2025 04:57PM UTC coverage: 91.516% (-0.4%) from 91.87%
18383183058

Pull #7689

github

web-flow
Merge 2f8b34e09 into 50143bdfe
Pull Request #7689: ISSUE-7677: Display late penalty selection

781 of 1640 branches covered (47.62%)

Branch coverage included in aggregate %.

20 of 26 new or added lines in 2 files covered. (76.92%)

37 existing lines in 1 file now uncovered.

42494 of 45647 relevant lines covered (93.09%)

119.82 hits per line

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

26.06
/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({loading}) {
10
  if (loading) {
210✔
11
    return (
66✔
12
      <div className="loading-spinner" data-testid="loading-spinner">
13
        <Grid
14
          visible={true}
15
          height="25"
16
          width="25"
17
          color="#31649B"
18
          aria-label="grid-loading"
19
          radius="12.5"
20
          wrapperStyle={{}}
21
          wrapperClass="grid-wrapper"
22
        />
23
      </div>
24
    );
25
  }
26

27
  return null;
144✔
28
}
29

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

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

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

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

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

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

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

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

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

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

262
export function customNoDataProps({state}) {
263
  return {loading: state.loading, data: state.data};
×
264
}
265

266
export function getTimeExtension(extension, timePeriods) {
267
  return timePeriods
17✔
268
    .map(key => {
269
      const val = extension[key];
68✔
270

271
      if (!val) {
68✔
272
        return null;
59✔
273
      }
274
      // don't build these strings dynamically or they will be missed by the i18n-tasks checkers.
275
      if (key === "weeks") {
9!
NEW
276
        return `${val} ${I18n.t("durations.weeks", {count: val})}`;
×
277
      } else if (key === "days") {
9!
278
        return `${val} ${I18n.t("durations.days", {count: val})}`;
9✔
NEW
279
      } else if (key === "hours") {
×
NEW
280
        return `${val} ${I18n.t("durations.hours", {count: val})}`;
×
NEW
281
      } else if (key === "minutes") {
×
NEW
282
        return `${val} ${I18n.t("durations.minutes", {count: val})}`;
×
283
      }
NEW
284
      return "";
×
285
    })
286
    .filter(Boolean)
287
    .join(", ");
288
}
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