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

MarkUsProject / Markus / 12738913203

12 Jan 2025 11:22PM UTC coverage: 91.774% (+0.04%) from 91.732%
12738913203

Pull #7381

github

web-flow
Merge 436ac3180 into 7081a5199
Pull Request #7381: Optimize Empty Submission Querying in graders_controller

622 of 1359 branches covered (45.77%)

Branch coverage included in aggregate %.

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

26 existing lines in 4 files now uncovered.

41191 of 44202 relevant lines covered (93.19%)

120.5 hits per line

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

65.19
/app/javascript/Components/markus_file_manager.jsx
1
/* MarkUs-specific customization of react-keyed-file-browser library.
2
 * Provides customized versions of the components in that library.
3
 */
4
import React from "react";
5
import ClassNames from "classnames";
6
import {HTML5Backend, NativeTypes} from "react-dnd-html5-backend";
7
import {DndProvider, DragSource, DropTarget} from "react-dnd";
8
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
9

10
import {
11
  RawFileBrowser,
12
  Headers,
13
  FileRenderers,
14
  BaseFileConnectors,
15
  FolderRenderers,
16
} from "react-keyed-file-browser";
17

18
class RawFileManager extends RawFileBrowser {
19
  handleActionBarAddFileClick = (event, selectedItem) => {
17✔
20
    event.preventDefault();
4✔
21
    this.props.onActionBarAddFileClick(this.folderTarget(selectedItem));
4✔
22
  };
23

24
  handleActionBarAddFolderClickSetSelection = (event, selectedItem) => {
17✔
25
    event.persist();
×
26
    const target = this.folderTarget(selectedItem);
×
27
    this.select(target, "folder");
×
28
    this.handleActionBarAddFolderClick(event);
×
29
  };
30

31
  handleActionBarSubmitURLClick = (event, selectedItem) => {
17✔
32
    event.preventDefault();
×
33
    this.props.onActionBarSubmitURLClick(this.folderTarget(selectedItem));
×
34
  };
35

36
  folderTarget = selectedItem => {
17✔
37
    // treat multiple selections as not targeting a folder
38
    const selectionIsFolder = !!selectedItem && selectedItem.relativeKey.endsWith("/");
4!
39
    if (selectedItem === null) {
4!
40
      return null;
4✔
41
    } else if (selectionIsFolder) {
×
42
      return selectedItem.relativeKey;
×
43
    } else {
44
      return selectedItem.relativeKey.substring(0, selectedItem.relativeKey.lastIndexOf("/") + 1);
×
45
    }
46
  };
47

48
  upload_or_submit_file_label = () => {
17✔
49
    const locale = this.props.isSubmittingItems ? "submit_the" : "upload_the";
38✔
50
    return I18n.t(locale, {item: I18n.t("file")});
38✔
51
  };
52

53
  renderActionBar(selectedItems) {
54
    // treat multiple selections the same as not targeting
55
    let selectedItem = selectedItems.length === 1 ? selectedItems[0] : null;
43✔
56
    const selectionIsFolder = !!selectedItem && selectedItem.relativeKey.endsWith("/");
43✔
57
    let actions = [];
43✔
58

59
    if (!this.props.readOnly && selectedItem) {
43✔
60
      // Something is selected. Build custom actions depending on what it is.
61
      if (selectedItem.action) {
8!
62
        // Selected item has an active action against it. Disable all other actions.
63
        let actionText;
64
        switch (selectedItem.action) {
×
65
          case "delete":
66
            actionText = "Deleting ...";
×
67
            break;
×
68

69
          case "rename":
70
            actionText = "Renaming ...";
×
71
            break;
×
72

73
          default:
74
            actionText = "Moving ...";
×
75
            break;
×
76
        }
77
        actions = (
×
78
          // TODO: Enable plugging in custom spinner.
79
          <div className="item-actions">
80
            {/*<i className="icon loading fa fa-circle-o-notch fa-spin" /> {actionText}*/}
81
          </div>
82
        );
83
      } else {
84
        if (
8!
85
          selectedItem &&
16!
86
          !this.props.disableActions.addFolder &&
87
          typeof this.props.onCreateFolder === "function" &&
88
          !this.state.nameFilter
89
        ) {
90
          actions.push(
×
91
            <li key="action-add-folder">
92
              <a
93
                onClick={event =>
94
                  this.handleActionBarAddFolderClickSetSelection(event, selectedItem)
×
95
                }
96
                href="#"
97
                role="button"
98
              >
99
                <FontAwesomeIcon icon="fa-solid fa-folder-plus" />
100
                {I18n.t("add_folder")}
101
              </a>
102
            </li>
103
          );
104
        }
105
        if (
8!
106
          selectedItem.keyDerived &&
16!
107
          !this.props.disableActions.rename &&
108
          ((selectionIsFolder && typeof this.props.onRenameFolder === "function") ||
109
            (!selectionIsFolder && typeof this.props.onRenameFile === "function"))
110
        ) {
UNCOV
111
          actions.push(
×
112
            <li key="action-rename">
113
              <a onClick={this.handleActionBarRenameClick} href="#" role="button">
114
                {I18n.t("rename")}
115
              </a>
116
            </li>
117
          );
118
        }
119
        if (
8!
120
          selectedItem.keyDerived &&
32!
121
          ((!selectionIsFolder &&
122
            typeof this.props.onDeleteFile === "function" &&
123
            !this.props.disableActions.deleteFile) ||
124
            (selectionIsFolder &&
125
              typeof this.props.onDeleteFolder === "function" &&
126
              !this.props.disableActions.deleteFolder))
127
        ) {
128
          actions.push(
8✔
129
            <li key="action-delete">
130
              <a onClick={this.handleActionBarDeleteClick} href="#" role="button">
131
                <FontAwesomeIcon icon="fa-solid fa-trash" />
132
                {I18n.t("delete")}
133
              </a>
134
            </li>
135
          );
136
        }
137
        if (this.props.enableUrlSubmit) {
8!
UNCOV
138
          actions.unshift(
×
139
            <li key="action-add-link">
140
              <a
UNCOV
141
                onClick={event => this.handleActionBarSubmitURLClick(event, selectedItem)}
×
142
                href="#"
143
                role="button"
144
              >
145
                <FontAwesomeIcon icon="fa-solid fa-link" />
146
                {I18n.t("submit_the", {item: I18n.t("submissions.student.link")})}
147
              </a>
148
            </li>
149
          );
150
        }
151
        // NEW
152
        actions.unshift(
8✔
153
          <li key="action-add-file>">
154
            <a
UNCOV
155
              onClick={event => this.handleActionBarAddFileClick(event, selectedItem)}
×
156
              href="#"
157
              role="button"
158
            >
159
              <FontAwesomeIcon icon="fa-solid fa-upload" />
160
              {this.upload_or_submit_file_label()}
161
            </a>
162
          </li>
163
        );
164
      }
165
    } else if (!this.props.readOnly) {
35✔
166
      // Nothing selected.
167
      if (
30✔
168
        !this.props.disableActions.addFolder &&
41✔
169
        typeof this.props.onCreateFolder === "function" &&
170
        !this.state.nameFilter
171
      ) {
172
        actions.push(
5✔
173
          <li key="action-add-folder">
174
            <a onClick={this.handleActionBarAddFolderClick} href="#" role="button">
175
              <FontAwesomeIcon icon="fa-solid fa-folder-plus" />
176
              &nbsp;{I18n.t("add_folder")}
177
            </a>
178
          </li>
179
        );
180
      }
181
      if (this.props.enableUrlSubmit) {
30!
UNCOV
182
        actions.unshift(
×
183
          <li key="action-add-link">
184
            <a
UNCOV
185
              onClick={event => this.handleActionBarSubmitURLClick(event, selectedItem)}
×
186
              href="#"
187
              role="button"
188
            >
189
              <FontAwesomeIcon icon="fa-solid fa-link" />
190
              {I18n.t("submit_the", {item: I18n.t("submissions.student.link")})}
191
            </a>
192
          </li>
193
        );
194
      }
195
      // NEW
196
      actions.unshift(
30✔
197
        <li key="action-add-file>">
198
          <a
199
            onClick={event => this.handleActionBarAddFileClick(event, selectedItem)}
4✔
200
            href="#"
201
            role="button"
202
          >
203
            <FontAwesomeIcon icon="fa-solid fa-upload" />
204
            {this.upload_or_submit_file_label()}
205
          </a>
206
        </li>
207
      );
208

209
      actions.push(
30✔
210
        <li key="action-delete" style={{color: "#8d8d8d"}}>
211
          <FontAwesomeIcon icon="fa-solid fa-trash" />
212
          &nbsp;{I18n.t("delete")}
213
        </li>
214
      );
215
    }
216

217
    if (this.props.downloadAllURL && !this.props.disableActions.downloadAll) {
43✔
218
      actions.unshift(
42✔
219
        <li key="action-download-all">
220
          <a href={this.props.downloadAllURL} role="button">
221
            <FontAwesomeIcon icon="fa-solid fa-download" />
222
            {I18n.t("download_the", {item: I18n.t("all")})}
223
          </a>
224
        </li>
225
      );
226
    }
227

228
    let actionList;
229
    if (actions.length) {
43!
230
      actionList = <ul className="item-actions">{actions}</ul>;
43✔
231
    } else {
UNCOV
232
      actionList = <div className="item-actions">&nbsp;</div>;
×
233
    }
234

235
    return <div className="action-bar">{actionList}</div>;
43✔
236
  }
237
}
238

239
class RawFileManagerHeader extends Headers.TableHeader {
240
  render() {
241
    const header = (
242
      <tr
60✔
243
        className={ClassNames("folder", {
244
          dragover: this.props.isOver,
245
          selected: this.props.isSelected,
246
        })}
247
      >
248
        <th>{I18n.t("attributes.filename")}</th>
249
        <th className="modified">{I18n.t("submissions.repo_browser.submitted_at")}</th>
250
        <th className="modified">{I18n.t("submissions.repo_browser.revised_by")}</th>
251
      </tr>
252
    );
253

254
    if (
60✔
255
      typeof this.props.browserProps.createFiles === "function" ||
64✔
256
      typeof this.props.browserProps.moveFile === "function" ||
257
      typeof this.props.browserProps.moveFolder === "function"
258
    ) {
259
      return this.props.connectDropTarget(header);
58✔
260
    } else {
261
      return header;
2✔
262
    }
263
  }
264
}
265

266
class FileManagerFile extends FileRenderers.RawTableFile {
267
  handleFileClick = event => {
24✔
UNCOV
268
    if (event) {
×
UNCOV
269
      event.preventDefault();
×
270
    }
271
  };
272

273
  handleItemClick = event => {
24✔
274
    // This disables the option to select multiple rows in the file manager
275
    // To re-enable multiple selection, remove this method entirely.
276
    event.stopPropagation();
4✔
277
    this.props.browserProps.select(this.props.fileKey, "file");
4✔
278
  };
279

280
  render() {
281
    let icon;
282
    if (this.getFileType() === "Image") {
88✔
283
      icon = <FontAwesomeIcon icon="fa-solid fa-file-image" />;
2✔
284
    } else if (this.getFileType() === "PDF") {
86✔
285
      icon = <FontAwesomeIcon icon="fa-solid fa-file-pdf" />;
20✔
286
    } else {
287
      icon = <FontAwesomeIcon icon="fa-solid fa-file" />;
66✔
288
    }
289

290
    const inAction = this.props.isDragging || this.props.action;
88✔
291

292
    let name;
293
    if (!inAction && this.props.isDeleting) {
88!
UNCOV
294
      name = (
×
295
        <form className="deleting" onSubmit={this.handleDeleteSubmit}>
296
          <a href={this.props.url || "#"} download="download" onClick={this.handleFileClick}>
×
297
            <FontAwesomeIcon icon="fa-solid fa-download" className="file-download" />
298
            {this.getName()}
299
          </a>
300
          <span>
301
            <button type="submit">
302
              <FontAwesomeIcon icon="fa-solid fa-trash" />
303
              {I18n.t("submissions.repo_browser.confirm_deletion")}
304
            </button>
305
          </span>
306
        </form>
307
      );
308
    } else if (!inAction && this.props.isRenaming) {
88!
UNCOV
309
      name = (
×
310
        <form className="renaming" onSubmit={this.handleRenameSubmit}>
311
          {icon}
312
          <input
313
            ref="newName"
314
            type="text"
315
            value={this.state.newName}
316
            onChange={this.handleNewNameChange}
317
            onBlur={this.handleCancelEdit}
318
            autoFocus
319
          />
320
        </form>
321
      );
322
    } else {
323
      name = (
88✔
324
        <React.Fragment>
325
          {icon}
326
          <span>{this.getName()}</span>
327
          <a
328
            href={this.props.url || "#"}
88!
329
            download={this.getName()}
330
            title={I18n.t("download_the", {item: this.getName()})}
331
          >
332
            <FontAwesomeIcon icon="fa-solid fa-download" className="file-download" />
333
          </a>
334
        </React.Fragment>
335
      );
336
    }
337

338
    let draggable = <div>{name}</div>;
88✔
339
    if (typeof this.props.browserProps.moveFile === "function") {
88!
UNCOV
340
      draggable = this.props.connectDragPreview(draggable);
×
341
    }
342

343
    let row = (
344
      <tr
88✔
345
        className={ClassNames("file", {
346
          pending: this.props.action,
347
          dragging: this.props.isDragging,
348
          dragover: this.props.isOver,
349
          selected: this.props.isSelected,
350
        })}
351
        onClick={this.handleItemClick}
352
        onDoubleClick={this.handleItemDoubleClick}
353
      >
354
        <td className="name">
355
          <div style={{paddingLeft: this.props.depth * 16 + "px"}}>{draggable}</div>
356
        </td>
357
        <td className="modified">
358
          {typeof this.props.submitted_date === "undefined" ? "-" : this.props.submitted_date}
88!
359
        </td>
360
        <td className="modified">{this.props.revision_by}</td>
361
      </tr>
362
    );
363

364
    return this.connectDND(row);
88✔
365
  }
366
}
367

368
FileManagerFile = DragSource(
4✔
369
  "file",
370
  BaseFileConnectors.dragSource,
371
  BaseFileConnectors.dragCollect
372
)(
373
  DropTarget(
374
    ["file", "folder", NativeTypes.FILE],
375
    BaseFileConnectors.targetSource,
376
    BaseFileConnectors.targetCollect
377
  )(FileManagerFile)
378
);
379

380
const FileManagerHeader = DragSource(
4✔
381
  "file",
382
  BaseFileConnectors.dragSource,
383
  BaseFileConnectors.dragCollect
384
)(
385
  DropTarget(
386
    ["file", "folder", NativeTypes.FILE],
387
    BaseFileConnectors.targetSource,
388
    BaseFileConnectors.targetCollect
389
  )(RawFileManagerHeader)
390
);
391

392
class FileManager extends React.Component {
393
  render() {
394
    return (
35✔
395
      <DndProvider backend={HTML5Backend}>
396
        <RawFileManager {...this.props} />
397
      </DndProvider>
398
    );
399
  }
400
}
401

402
FileManager.defaultProps = {
4✔
403
  headerRenderer: FileManagerHeader,
404
  fileRenderer: FileManagerFile,
405
  icons: {
406
    Folder: <FontAwesomeIcon icon="fa-solid fa-folder" />,
407
    FolderOpen: <FontAwesomeIcon icon="fa-solid fa-folder-open" />,
408
    Delete: <FontAwesomeIcon icon="fa-solid fa-trash" />,
409
  },
410
  disableActions: {},
411
};
412

413
export default FileManager;
414
export {FileManagerHeader, FileManagerFile};
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