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

CBIIT / crdc-datahub-ui / 8848801071

26 Apr 2024 01:08PM UTC coverage: 23.605%. First build
8848801071

Pull #337

github

web-flow
Merge 0a385c8c2 into 7478acc8f
Pull Request #337: CRDCDH-922 Cross Submission Validation

644 of 3516 branches covered (18.32%)

Branch coverage included in aggregate %.

42 of 48 new or added lines in 6 files covered. (87.5%)

1166 of 4152 relevant lines covered (28.08%)

91.99 hits per line

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

0.0
/src/content/dataSubmissions/DataSubmissionActions.tsx
1
import { useState } from "react";
2
import { useMutation } from "@apollo/client";
3
import { LoadingButton } from "@mui/lab";
4
import { Button, OutlinedInput, Stack, Typography, styled } from "@mui/material";
5
import { useAuthContext } from "../../components/Contexts/AuthContext";
6
import CustomDialog from "../../components/Shared/Dialog";
7
import { EXPORT_SUBMISSION, ExportSubmissionResp } from "../../graphql";
8
import { ReleaseInfo } from "../../utils";
9

10
const StyledActionWrapper = styled(Stack)(() => ({
×
11
  justifyContent: "center",
12
  alignItems: "center",
13
}));
14

15
const StyledOutlinedInput = styled(OutlinedInput)(() => ({
×
16
  borderRadius: "8px",
17
  backgroundColor: "#fff",
18
  color: "#083A50",
19
  "& .MuiInputBase-input": {
20
    fontWeight: 400,
21
    fontSize: "16px",
22
    fontFamily: "'Nunito', 'Rubik', sans-serif",
23
    lineHeight: "19.6px",
24
    padding: "12px",
25
    height: "20px",
26
  },
27
  "&.MuiInputBase-multiline": {
28
    padding: "12px",
29
  },
30
  "&.MuiInputBase-multiline .MuiInputBase-input": {
31
    lineHeight: "25px",
32
    padding: 0,
33
  },
34
  "& .MuiOutlinedInput-notchedOutline": {
35
    borderColor: "#6B7294",
36
  },
37
  "&.Mui-focused .MuiOutlinedInput-notchedOutline": {
38
    border: "1px solid #209D7D",
39
    boxShadow:
40
      "2px 2px 4px 0px rgba(38, 184, 147, 0.10), -1px -1px 6px 0px rgba(38, 184, 147, 0.20)",
41
  },
42
  "& .MuiInputBase-input::placeholder": {
43
    color: "#87878C",
44
    fontWeight: 400,
45
    opacity: 1,
46
  },
47
}));
48

49
const StyledLoadingButton = styled(LoadingButton)(() => ({
×
50
  minWidth: "137px",
51
  width: "fit-content",
52
  padding: "10px",
53
  borderRadius: "8px",
54
  textAlign: "center",
55
  fontFamily: "'Nunito', 'Rubik', sans-serif",
56
  fontSize: "16px",
57
  fontStyle: "normal",
58
  lineHeight: "24px",
59
  letterSpacing: "0.32px",
60
  textTransform: "initial",
61
  zIndex: 3,
62
}));
63

64
const StyledDialog = styled(CustomDialog)({
×
65
  "& .MuiDialog-paper": {
66
    maxWidth: "none",
67
    borderRadius: "8px",
68
    width: "567px !important",
69
  },
70
});
71

72
const StyledDialogText = styled(Typography)({
×
73
  fontWeight: 400,
74
  fontSize: "16px",
75
  fontFamily: "'Nunito', 'Rubik', sans-serif",
76
  lineHeight: "19.6px",
77
});
78

79
export type ActiveDialog =
80
  | "Submit"
81
  | "Release"
82
  | "ReleaseCrossValidation"
83
  | "Withdraw"
84
  | "Reject"
85
  | "Complete"
86
  | "Cancel";
87

88
type ActionConfig = {
89
  roles: User["role"][];
90
  statuses: SubmissionStatus[];
91
};
92

93
type ActionKey =
94
  | "Submit"
95
  | "Release"
96
  | "Withdraw"
97
  | "SubmittedReject"
98
  | "ReleasedReject"
99
  | "Complete"
100
  | "Cancel"
101
  | "Archive";
102

103
const actionConfig: Record<ActionKey, ActionConfig> = {
×
104
  Submit: {
105
    roles: ["Submitter", "Organization Owner", "Data Curator", "Admin"],
106
    statuses: ["In Progress", "Withdrawn"],
107
  },
108
  Release: {
109
    roles: ["Data Curator", "Admin"],
110
    statuses: ["Submitted"],
111
  },
112
  Withdraw: {
113
    roles: ["Submitter", "Organization Owner"],
114
    statuses: ["Submitted"],
115
  },
116
  SubmittedReject: {
117
    roles: ["Data Curator", "Admin"],
118
    statuses: ["Submitted"],
119
  },
120
  ReleasedReject: {
121
    roles: ["Data Commons POC", "Admin"],
122
    statuses: ["Released"],
123
  },
124
  Complete: {
125
    roles: ["Data Curator", "Admin", "Data Commons POC"],
126
    statuses: ["Released"],
127
  },
128
  Cancel: {
129
    roles: ["Submitter", "Organization Owner", "Data Curator", "Admin"],
130
    statuses: ["New", "In Progress"],
131
  },
132
  Archive: {
133
    roles: ["Data Curator", "Admin"],
134
    statuses: ["Completed"],
135
  },
136
};
137

138
type SubmitActionButton = {
139
  label: "Submit" | "Admin Submit";
140
  disable: boolean;
141
};
142

143
type Props = {
144
  submission: Submission;
145
  submitActionButton: SubmitActionButton;
146
  releaseActionButton: ReleaseInfo;
147
  onAction: (action: SubmissionAction, reviewComment?: string) => Promise<void>;
148
  onError: (message: string) => void;
149
};
150

NEW
151
const DataSubmissionActions = ({
×
152
  submission,
153
  submitActionButton,
154
  releaseActionButton,
155
  onAction,
156
  onError,
157
}: Props) => {
158
  const { user } = useAuthContext();
×
159

160
  const [currentDialog, setCurrentDialog] = useState<ActiveDialog | null>(null);
×
161
  const [action, setAction] = useState<SubmissionAction | null>(null);
×
162
  const [reviewComment, setReviewComment] = useState("");
×
163

164
  const [exportSubmission] = useMutation<ExportSubmissionResp>(EXPORT_SUBMISSION, {
×
165
    context: { clientName: "backend" },
166
    fetchPolicy: "no-cache",
167
  });
168

169
  const handleExportSubmission = async (): Promise<boolean> => {
×
170
    if (!submission?._id) {
×
171
      return false;
×
172
    }
173

174
    try {
×
175
      const { data: d, errors } = await exportSubmission({
×
176
        variables: {
177
          _id: submission._id,
178
        },
179
      });
180
      if (errors || !d?.exportSubmission?.success) {
×
181
        throw new Error();
×
182
      }
183
      return d.exportSubmission.success;
×
184
    } catch (err) {
185
      onError("Unable to export submission.");
×
186
    }
187

188
    return false;
×
189
  };
190

191
  const handleOnAction = async (action: SubmissionAction) => {
×
192
    if (currentDialog) {
×
193
      setCurrentDialog(null);
×
194
    }
195
    setAction(action);
×
196
    if (action === "Release") {
×
197
      const isExported = await handleExportSubmission();
×
198
      if (!isExported) {
×
199
        setAction(null);
×
200
        return;
×
201
      }
202
    }
203
    if (typeof onAction === "function") {
×
204
      await onAction(action, reviewComment || null);
×
205
    }
206
    setAction(null);
×
207
    setReviewComment("");
×
208
  };
209

210
  const onOpenDialog = (dialog: ActiveDialog) => {
×
211
    setCurrentDialog(dialog);
×
212
  };
213

214
  const onCloseDialog = () => {
×
215
    setCurrentDialog(null);
×
216
    setReviewComment("");
×
217
  };
218

219
  const canShowAction = (actionKey: ActionKey) => {
×
220
    const config = actionConfig[actionKey];
×
221
    return config?.statuses?.includes(submission?.status) && config?.roles?.includes(user?.role);
×
222
  };
223

224
  const handleCommentChange = (event: React.ChangeEvent<HTMLInputElement>) => {
×
225
    const val = event?.target?.value || "";
×
226
    setReviewComment(val);
×
227
  };
228

229
  return (
×
230
    <StyledActionWrapper direction="row" spacing={2}>
231
      {/* Action Buttons */}
232
      {canShowAction("Submit") ? (
×
233
        <StyledLoadingButton
234
          variant="contained"
235
          color="primary"
236
          onClick={() => onOpenDialog("Submit")}
×
237
          loading={action === "Submit"}
238
          disabled={submitActionButton?.disable || (action && action !== "Submit")}
×
239
        >
240
          {submitActionButton?.label || "Submit"}
×
241
        </StyledLoadingButton>
242
      ) : null}
243
      {canShowAction("Release") ? (
×
244
        <StyledLoadingButton
245
          variant="contained"
246
          color="primary"
247
          onClick={() =>
NEW
248
            onOpenDialog(releaseActionButton.requireAlert ? "ReleaseCrossValidation" : "Release")
×
249
          }
250
          loading={action === "Release"}
251
          disabled={(action && action !== "Release") || releaseActionButton.disable}
×
252
        >
253
          Release
254
        </StyledLoadingButton>
255
      ) : null}
256
      {canShowAction("Complete") ? (
×
257
        <StyledLoadingButton
258
          variant="contained"
259
          color="primary"
260
          onClick={() => onOpenDialog("Complete")}
×
261
          loading={action === "Complete"}
262
          disabled={action && action !== "Complete"}
×
263
        >
264
          Complete
265
        </StyledLoadingButton>
266
      ) : null}
267
      {canShowAction("Archive") ? (
×
268
        <StyledLoadingButton
269
          variant="contained"
270
          color="primary"
271
          onClick={() => handleOnAction("Archive")}
×
272
          loading={action === "Archive"}
273
          disabled={action && action !== "Archive"}
×
274
        >
275
          Archive
276
        </StyledLoadingButton>
277
      ) : null}
278
      {canShowAction("Withdraw") ? (
×
279
        <StyledLoadingButton
280
          variant="contained"
281
          color="error"
282
          onClick={() => onOpenDialog("Withdraw")}
×
283
          loading={action === "Withdraw"}
284
          disabled={action && action !== "Withdraw"}
×
285
        >
286
          Withdraw
287
        </StyledLoadingButton>
288
      ) : null}
289
      {canShowAction("SubmittedReject") || canShowAction("ReleasedReject") ? (
×
290
        <StyledLoadingButton
291
          variant="contained"
292
          color="error"
293
          onClick={() => onOpenDialog("Reject")}
×
294
          loading={action === "Reject"}
295
          disabled={action && action !== "Reject"}
×
296
        >
297
          Reject
298
        </StyledLoadingButton>
299
      ) : null}
300
      {canShowAction("Cancel") ? (
×
301
        <StyledLoadingButton
302
          variant="contained"
303
          color="error"
304
          onClick={() => onOpenDialog("Cancel")}
×
305
          loading={action === "Cancel"}
306
          disabled={action && action !== "Cancel"}
×
307
        >
308
          Cancel
309
        </StyledLoadingButton>
310
      ) : null}
311

312
      {/* Submit Dialog */}
313
      <StyledDialog
314
        open={currentDialog === "Submit" && submitActionButton.label === "Submit"}
×
315
        onClose={onCloseDialog}
316
        title="Submit Data Submission"
317
        actions={
318
          <>
319
            <Button onClick={onCloseDialog} disabled={!!action}>
320
              No
321
            </Button>
322
            <LoadingButton
323
              onClick={() => handleOnAction("Submit")}
×
324
              loading={!!action}
325
              color="error"
326
              autoFocus
327
            >
328
              Yes
329
            </LoadingButton>
330
          </>
331
        }
332
      >
333
        <StyledDialogText variant="body2">
334
          This action will lock your submission and it will no longer accept updates to the data.
335
          Are you sure you want to proceed?
336
        </StyledDialogText>
337
      </StyledDialog>
338

339
      {/* Admin Submit Dialog */}
340
      <StyledDialog
341
        open={currentDialog === "Submit" && submitActionButton.label === "Admin Submit"}
×
342
        onClose={onCloseDialog}
343
        title="Admin Submit Data Submission"
344
        actions={
345
          <Stack direction="row" marginTop="24px">
346
            <Button onClick={onCloseDialog} disabled={!!action}>
347
              Cancel
348
            </Button>
349
            <LoadingButton
350
              onClick={() => handleOnAction("Submit")}
×
351
              loading={!!action}
352
              disabled={reviewComment?.trim()?.length <= 0}
353
              autoFocus
354
            >
355
              Confirm to Submit
356
            </LoadingButton>
357
          </Stack>
358
        }
359
      >
360
        <StyledOutlinedInput
361
          value={reviewComment}
362
          onChange={handleCommentChange}
363
          placeholder="Enter comments here. Max of 500 characters"
364
          inputProps={{ "aria-label": "Admin override justification" }}
365
          slotProps={{ input: { minLength: 1, maxLength: 500 } }}
366
          minRows={4}
367
          maxRows={4}
368
          multiline
369
          fullWidth
370
          required
371
        />
372
      </StyledDialog>
373

374
      {/* Release Dialog (default) */}
375
      <StyledDialog
376
        open={currentDialog === "Release"}
377
        onClose={onCloseDialog}
378
        title="Release Data Submission"
379
        actions={
380
          <>
381
            <Button onClick={onCloseDialog} disabled={!!action}>
382
              No
383
            </Button>
384
            <LoadingButton
385
              onClick={() => handleOnAction("Release")}
×
386
              loading={!!action}
387
              color="error"
388
              autoFocus
389
            >
390
              Yes
391
            </LoadingButton>
392
          </>
393
        }
394
      >
395
        <StyledDialogText variant="body2">
396
          This action will release this submission to data commons and it can no longer accept
397
          changes to the data. Are you sure you want to proceed?
398
        </StyledDialogText>
399
      </StyledDialog>
400

401
      {/* Release dialog (cross-validation) */}
402
      <StyledDialog
403
        open={currentDialog === "ReleaseCrossValidation"}
404
        onClose={onCloseDialog}
405
        title="Release Data Submission"
406
        actions={
407
          <>
408
            <Button onClick={onCloseDialog} disabled={!!action}>
409
              Cancel
410
            </Button>
411
            <LoadingButton
NEW
412
              onClick={() => handleOnAction("Release")}
×
413
              loading={!!action}
414
              color="error"
415
              autoFocus
416
            >
417
              Confirm Release
418
            </LoadingButton>
419
          </>
420
        }
421
      >
422
        <StyledDialogText variant="body2">
423
          There are other data submissions for the same study currently ongoing. Are you sure you
424
          want to release this data submission to Data Commons?
425
        </StyledDialogText>
426
      </StyledDialog>
427

428
      {/* Cancel Dialog */}
429
      <StyledDialog
430
        open={currentDialog === "Cancel"}
431
        onClose={onCloseDialog}
432
        title="Cancel Data Submission"
433
        actions={
434
          <>
435
            <Button onClick={onCloseDialog} disabled={!!action}>
436
              No
437
            </Button>
438
            <LoadingButton
439
              onClick={() => handleOnAction("Cancel")}
×
440
              loading={!!action}
441
              color="error"
442
              autoFocus
443
            >
444
              Yes
445
            </LoadingButton>
446
          </>
447
        }
448
      >
449
        <StyledDialogText variant="body2">
450
          This action will remove this submission and it will no longer be accessible. Are you sure
451
          you want to proceed?
452
        </StyledDialogText>
453
      </StyledDialog>
454

455
      {/* Withdraw Dialog */}
456
      <StyledDialog
457
        open={currentDialog === "Withdraw"}
458
        onClose={onCloseDialog}
459
        title="Withdraw Data Submission"
460
        actions={
461
          <>
462
            <Button onClick={onCloseDialog} disabled={!!action}>
463
              No
464
            </Button>
465
            <LoadingButton
466
              onClick={() => handleOnAction("Withdraw")}
×
467
              loading={!!action}
468
              color="error"
469
              autoFocus
470
            >
471
              Yes
472
            </LoadingButton>
473
          </>
474
        }
475
      >
476
        <StyledDialogText variant="body2">
477
          This action will halt the data curation process and give control back to you if you wish
478
          to update the data within the submission. Are you certain you want to proceed?
479
        </StyledDialogText>
480
      </StyledDialog>
481

482
      {/* Reject Dialog */}
483
      <StyledDialog
484
        open={currentDialog === "Reject"}
485
        onClose={onCloseDialog}
486
        title="Reject Data Submission"
487
        actions={
488
          <Stack direction="row" marginTop="24px">
489
            <Button onClick={onCloseDialog} disabled={!!action}>
490
              Cancel
491
            </Button>
492
            <LoadingButton
493
              onClick={() => handleOnAction("Reject")}
×
494
              loading={!!action}
495
              disabled={reviewComment?.trim()?.length <= 0}
496
              color="error"
497
              autoFocus
498
            >
499
              Confirm to Reject
500
            </LoadingButton>
501
          </Stack>
502
        }
503
      >
504
        <StyledOutlinedInput
505
          value={reviewComment}
506
          onChange={handleCommentChange}
507
          placeholder="Enter comments here. Max of 500 characters"
508
          inputProps={{ "aria-label": "Reject justification" }}
509
          slotProps={{ input: { minLength: 1, maxLength: 500 } }}
510
          minRows={4}
511
          maxRows={4}
512
          multiline
513
          fullWidth
514
          required
515
        />
516
      </StyledDialog>
517

518
      {/* Complete Dialog */}
519
      <StyledDialog
520
        open={currentDialog === "Complete"}
521
        onClose={onCloseDialog}
522
        title="Complete Data Submission"
523
        actions={
524
          <>
525
            <Button onClick={onCloseDialog} disabled={!!action}>
526
              No
527
            </Button>
528
            <LoadingButton
529
              onClick={() => handleOnAction("Complete")}
×
530
              loading={!!action}
531
              color="error"
532
              autoFocus
533
            >
534
              Yes
535
            </LoadingButton>
536
          </>
537
        }
538
      >
539
        <StyledDialogText variant="body2">
540
          This action will close out the submission and start close out activities. Are you sure you
541
          want to proceed?
542
        </StyledDialogText>
543
      </StyledDialog>
544
    </StyledActionWrapper>
545
  );
546
};
547

548
export default DataSubmissionActions;
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