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

CBIIT / crdc-datahub-ui / 8664677531

12 Apr 2024 03:50PM UTC coverage: 21.416%. First build
8664677531

Pull #340

github

web-flow
Merge 05288c2e9 into 82b3a922d
Pull Request #340: CRDCDH-1028 Redesign Submission Flow

553 of 3476 branches covered (15.91%)

Branch coverage included in aggregate %.

0 of 23 new or added lines in 5 files covered. (0.0%)

1071 of 4107 relevant lines covered (26.08%)

91.11 hits per line

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

0.0
/src/content/dataSubmissions/DataSubmission.tsx
1
import { FC, useEffect, useMemo, useRef, useState } from "react";
2
import { useLazyQuery, useMutation, useQuery } from "@apollo/client";
3
import {
4
  Alert,
5
  Box,
6
  Button,
7
  Card,
8
  CardActions,
9
  CardContent,
10
  Container,
11
  IconButton,
12
  Stack,
13
  Tabs,
14
  Typography,
15
  styled,
16
} from "@mui/material";
17

18
import { isEqual } from "lodash";
19
import { useSnackbar, VariantType } from "notistack";
20
import bannerSvg from "../../assets/dataSubmissions/dashboard_banner.svg";
21
import summaryBannerSvg from "../../assets/dataSubmissions/summary_banner.png";
22
import LinkTab from "../../components/DataSubmissions/LinkTab";
23
import DataUpload from "../../components/DataSubmissions/DataUpload";
24
import {
25
  GET_SUBMISSION,
26
  LIST_BATCHES,
27
  SUBMISSION_ACTION,
28
  GetSubmissionResp,
29
  ListBatchesResp,
30
  SubmissionActionResp,
31
} from "../../graphql";
32
import DataSubmissionSummary from "../../components/DataSubmissions/DataSubmissionSummary";
33
import GenericTable, {
34
  Column,
35
  FetchListing,
36
  TableMethods,
37
} from "../../components/DataSubmissions/GenericTable";
38
import { FormatDate } from "../../utils";
39
import DataSubmissionActions from "./DataSubmissionActions";
40
import QualityControl from "./QualityControl";
41
import { ReactComponent as CopyIconSvg } from "../../assets/icons/copy_icon_2.svg";
42
import ErrorDialog from "./ErrorDialog";
43
import BatchTableContext from "./Contexts/BatchTableContext";
44
import DataSubmissionStatistics from "../../components/DataSubmissions/ValidationStatistics";
45
import ValidationControls from "../../components/DataSubmissions/ValidationControls";
46
import { useAuthContext } from "../../components/Contexts/AuthContext";
47
import FileListDialog from "./FileListDialog";
48
import { shouldDisableSubmit } from "../../utils/dataSubmissionUtils";
49
import usePageTitle from "../../hooks/usePageTitle";
50
import BackButton from "../../components/DataSubmissions/BackButton";
51
import SubmittedData from "./SubmittedData";
52
import { UserGuide } from "../../components/DataSubmissions/UserGuide";
53

54
const StyledBanner = styled("div")(({ bannerSrc }: { bannerSrc: string }) => ({
×
55
  background: `url(${bannerSrc})`,
56
  backgroundBlendMode: "luminosity, normal",
57
  backgroundSize: "cover",
58
  backgroundRepeat: "no-repeat",
59
  backgroundPosition: "top",
60
  width: "100%",
61
  height: "295px",
62
  display: "flex",
63
  justifyContent: "center",
64
  alignItems: "center",
65
  position: "relative",
66
  zIndex: 0,
67
}));
68

69
const StyledBannerContentContainer = styled(Container)(({ padding }: { padding?: string }) => ({
×
70
  "&.MuiContainer-root": {
71
    padding: padding || "58px 73px 186px",
×
72
    marginTop: "-295px",
73
    width: "100%",
74
    height: "100%",
75
    position: "relative",
76
    zIndex: 1,
77
  },
78
}));
79

80
const StyledCard = styled(Card)(() => ({
×
81
  borderRadius: "8px",
82
  backgroundColor: "#FFFFFF",
83
  padding: 0,
84
  // boxShadow: "0px -5px 35px 0px rgba(53, 96, 160, 0.30)",
85
  "& .MuiCardContent-root": {
86
    padding: 0,
87
  },
88
  "& .MuiCardActions-root": {
89
    justifyContent: "center",
90
    alignItems: "center",
91
    backgroundColor: "#FFFFFF",
92
    paddingTop: "34px",
93
    paddingBottom: "41px",
94
    position: "relative",
95
  },
96
  "&.MuiPaper-root": {
97
    border: "1px solid #6CACDA",
98
    borderTopRightRadius: 0,
99
    borderTopLeftRadius: 0,
100
    overflow: "visible",
101
  },
102
  "&::after": {
103
    content: '""',
104
    position: "absolute",
105
    zIndex: 1,
106
    bottom: 120,
107
    left: 0,
108
    pointerEvents: "none",
109
    backgroundImage: "linear-gradient(to bottom, rgba(255,255,255,0), rgba(251,253,255, 1) 20%)",
110
    width: "100%",
111
    height: "360px",
112
  },
113
}));
114

115
const StyledMainContentArea = styled("div")(() => ({
×
116
  position: "relative",
117
  zIndex: 2,
118
  borderRadius: 0,
119
  minHeight: "300px",
120
  padding: "21px 40px 0",
121
}));
122

123
const StyledCardActions = styled(CardActions)(() => ({
×
124
  "&.MuiCardActions-root": {
125
    paddingTop: 0,
126
  },
127
}));
128

129
const StyledTabs = styled(Tabs)(() => ({
×
130
  position: "relative",
131
  display: "flex",
132
  alignItems: "flex-end",
133
  "& .MuiTabs-flexContainer": {
134
    justifyContent: "center",
135
  },
136
  "& .MuiTabs-indicator": {
137
    display: "none !important",
138
  },
139
  "&::before": {
140
    content: '""',
141
    position: "absolute",
142
    bottom: 0,
143
    left: 0,
144
    right: 0,
145
    borderBottom: "1.25px solid #6CACDA",
146
    zIndex: 1,
147
  },
148
}));
149

150
const StyledAlert = styled(Alert)({
×
151
  fontWeight: 400,
152
  fontSize: "16px",
153
  fontFamily: "'Nunito', 'Rubik', sans-serif",
154
  lineHeight: "19.6px",
155
  scrollMarginTop: "64px",
156
});
157

158
const StyledWrapper = styled("div")({
×
159
  background: "#FBFDFF",
160
});
161

162
const StyledCardContent = styled(CardContent)({
×
163
  background: `url(${summaryBannerSvg})`,
164
  backgroundSize: "auto",
165
  backgroundRepeat: "no-repeat",
166
  backgroundPosition: "top",
167
});
168

169
const StyledRejectedStatus = styled("div")(() => ({
×
170
  color: "#B54717",
171
  fontWeight: 600,
172
}));
173

174
const StyledCopyWrapper = styled(Stack)(() => ({
×
175
  height: "42px",
176
  width: "fit-content",
177
  minWidth: "342px",
178
  padding: "11px 20px",
179
  borderRadius: "8px 8px 0px 0px",
180
  borderTop: "1.25px solid #6DADDB",
181
  borderRight: "1.25px solid #6DADDB",
182
  borderLeft: "1.25px solid #6DADDB",
183
  background: "#EAF5F8",
184
}));
185

186
const StyledCopyLabel = styled(Typography)(() => ({
×
187
  color: "#125868",
188
  fontFamily: "'Nunito', 'Rubik', sans-serif",
189
  fontSize: "12px",
190
  fontStyle: "normal",
191
  fontWeight: 800,
192
  lineHeight: "19.6px",
193
  letterSpacing: "0.24px",
194
  textTransform: "uppercase",
195
}));
196

197
const StyledCopyValue = styled(Typography)(() => ({
×
198
  color: "#125868",
199
  fontFamily: "'Nunito', 'Rubik', sans-serif",
200
  fontSize: "16px",
201
  fontStyle: "normal",
202
  fontWeight: 400,
203
  lineHeight: "19.6px",
204
  letterSpacing: "0.32px",
205
}));
206

207
const StyledCopyIDButton = styled(IconButton)(() => ({
×
208
  color: "#000000",
209
  padding: 0,
210
  "&.MuiIconButton-root.Mui-disabled": {
211
    color: "#B0B0B0",
212
  },
213
}));
214

215
const StyledErrorDetailsButton = styled(Button)(() => ({
×
216
  color: "#0B6CB1",
217
  fontFamily: "Inter",
218
  fontSize: "16px",
219
  fontStyle: "normal",
220
  fontWeight: 600,
221
  lineHeight: "19px",
222
  textDecorationLine: "underline",
223
  textTransform: "none",
224
  padding: 0,
225
  justifyContent: "flex-start",
226
  "&:hover": {
227
    background: "transparent",
228
    textDecorationLine: "underline",
229
  },
230
}));
231

232
const StyledFileCountButton = styled(Button)(() => ({
×
233
  color: "#0B6CB1",
234
  fontFamily: "Inter",
235
  fontSize: "16px",
236
  fontStyle: "normal",
237
  fontWeight: 600,
238
  lineHeight: "19px",
239
  textDecorationLine: "underline",
240
  textTransform: "none",
241
  padding: 0,
242
  justifyContent: "flex-start",
243
  "&:hover": {
244
    background: "transparent",
245
    textDecorationLine: "underline",
246
  },
247
}));
248

NEW
249
const StyledFlowContainer = styled(Box)({
×
250
  padding: "27px 59px 59px 60px",
251
});
252

253
const columns: Column<Batch>[] = [
×
254
  {
255
    label: "Batch ID",
256
    renderValue: (data) => data.displayID,
×
257
    field: "displayID",
258
  },
259
  {
260
    label: "Upload Type",
261
    renderValue: (data) => (data?.type !== "metadata" ? "-" : data?.metadataIntention),
×
262
    field: "metadataIntention",
263
  },
264
  {
265
    label: "Batch Type",
266
    renderValue: (data) => <Box textTransform="capitalize">{data?.type}</Box>,
×
267
    field: "type",
268
  },
269
  {
270
    label: "File Count",
271
    renderValue: (data) => (
272
      <BatchTableContext.Consumer>
×
273
        {({ handleOpenFileListDialog }) => (
274
          <StyledFileCountButton
×
275
            onClick={() => handleOpenFileListDialog && handleOpenFileListDialog(data)}
×
276
            variant="text"
277
            disableRipple
278
            disableTouchRipple
279
            disableFocusRipple
280
          >
281
            {Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(data?.fileCount || 0)}
×
282
          </StyledFileCountButton>
283
        )}
284
      </BatchTableContext.Consumer>
285
    ),
286
    field: "fileCount",
287
  },
288
  {
289
    label: "Status",
290
    renderValue: (data) => (
291
      <Box textTransform="capitalize">
×
292
        {data.status === "Failed" ? (
×
293
          <StyledRejectedStatus>{data.status}</StyledRejectedStatus>
294
        ) : (
295
          data.status
296
        )}
297
      </Box>
298
    ),
299
    field: "status",
300
  },
301
  {
302
    label: "Uploaded Date",
303
    renderValue: (data) =>
304
      data?.createdAt ? `${FormatDate(data.createdAt, "MM-DD-YYYY [at] hh:mm A")}` : "",
×
305
    field: "createdAt",
306
    default: true,
307
    sx: {
308
      minWidth: "240px",
309
    },
310
  },
311
  {
312
    label: "Upload Errors",
313
    renderValue: (data) => (
314
      <BatchTableContext.Consumer>
×
315
        {({ handleOpenErrorDialog }) => {
316
          if (!data?.errors?.length) {
×
317
            return null;
×
318
          }
319

320
          return (
×
321
            <StyledErrorDetailsButton
322
              onClick={() => handleOpenErrorDialog && handleOpenErrorDialog(data)}
×
323
              variant="text"
324
              disableRipple
325
              disableTouchRipple
326
              disableFocusRipple
327
            >
328
              {data.errors?.length > 0
×
329
                ? `${data.errors.length} ${data.errors.length === 1 ? "Error" : "Errors"}`
×
330
                : ""}
331
            </StyledErrorDetailsButton>
332
          );
333
        }}
334
      </BatchTableContext.Consumer>
335
    ),
336
    field: "errors",
337
    sortDisabled: true,
338
  },
339
];
340

341
const URLTabs = {
×
342
  DATA_ACTIVITY: "data-activity",
343
  VALIDATION_RESULTS: "validation-results",
344
  SUBMITTED_DATA: "submitted-data",
345
};
346

347
const submissionLockedStatuses: SubmissionStatus[] = [
×
348
  "Submitted",
349
  "Released",
350
  "Completed",
351
  "Canceled",
352
  "Archived",
353
];
354

355
type Props = {
356
  submissionId: string;
357
  tab: string;
358
};
359

360
const DataSubmission: FC<Props> = ({ submissionId, tab = URLTabs.DATA_ACTIVITY }) => {
×
361
  usePageTitle(`Data Submission ${submissionId || ""}`);
×
362

363
  const { user } = useAuthContext();
×
364
  const { enqueueSnackbar } = useSnackbar();
×
365

366
  const [batches, setBatches] = useState<Batch[]>([]);
×
367
  const [totalBatches, setTotalBatches] = useState<number>(0);
×
368
  const [hasUploadingBatches, setHasUploadingBatches] = useState<boolean>(false);
×
369
  const [prevBatchFetch, setPrevBatchFetch] = useState<FetchListing<Batch>>(null);
×
370
  const [batchRefreshTimeout, setBatchRefreshTimeout] = useState<NodeJS.Timeout>(null);
×
371
  const [error, setError] = useState<string>(null);
×
372
  const [loading, setLoading] = useState<boolean>(false);
×
373
  const [openErrorDialog, setOpenErrorDialog] = useState<boolean>(false);
×
374
  const [openFileListDialog, setOpenFileListDialog] = useState<boolean>(false);
×
375
  const [selectedRow, setSelectedRow] = useState<Batch | null>(null);
×
376

377
  const {
378
    data,
379
    error: submissionError,
380
    startPolling,
381
    stopPolling,
382
    refetch: getSubmission,
383
  } = useQuery<GetSubmissionResp>(GET_SUBMISSION, {
×
384
    variables: { id: submissionId },
385
    context: { clientName: "backend" },
386
    fetchPolicy: "no-cache",
387
  });
388

389
  const tableRef = useRef<TableMethods>(null);
×
390
  const isValidTab = tab && Object.values(URLTabs).includes(tab);
×
391
  const submitInfo: { disable: boolean; isAdminOverride: boolean } = useMemo(() => {
×
392
    const canSubmitRoles: User["role"][] = [
×
393
      "Submitter",
394
      "Organization Owner",
395
      "Data Curator",
396
      "Admin",
397
    ];
398
    if (!data?.getSubmission?._id || !canSubmitRoles.includes(user?.role)) {
×
399
      return { disable: true, isAdminOverride: false };
×
400
    }
401

402
    return shouldDisableSubmit(data.getSubmission, user?.role);
×
403
  }, [data?.getSubmission, user]);
404

405
  const [listBatches] = useLazyQuery<ListBatchesResp>(LIST_BATCHES, {
×
406
    context: { clientName: "backend" },
407
    fetchPolicy: "no-cache",
408
  });
409

410
  const [submissionAction] = useMutation<SubmissionActionResp>(SUBMISSION_ACTION, {
×
411
    context: { clientName: "backend" },
412
    fetchPolicy: "no-cache",
413
  });
414

415
  const handleFetchBatches = async (fetchListing: FetchListing<Batch>, force: boolean) => {
×
416
    const { first, offset, sortDirection, orderBy } = fetchListing || {};
×
417
    if (!submissionId) {
×
418
      setError("Invalid submission ID provided.");
×
419
      return;
×
420
    }
421
    if (!force && batches?.length > 0 && isEqual(fetchListing, prevBatchFetch)) {
×
422
      return;
×
423
    }
424

425
    setPrevBatchFetch(fetchListing);
×
426

427
    try {
×
428
      setLoading(true);
×
429
      const { data: newBatchFiles, error: batchFilesError } = await listBatches({
×
430
        variables: {
431
          submissionID: submissionId,
432
          first,
433
          offset,
434
          sortDirection,
435
          orderBy,
436
        },
437
        context: { clientName: "backend" },
438
        fetchPolicy: "no-cache",
439
      });
440
      if (batchFilesError || !newBatchFiles?.listBatches) {
×
441
        setError("Unable to retrieve batch data.");
×
442
        return;
×
443
      }
444
      setBatches(newBatchFiles.listBatches.batches);
×
445
      setTotalBatches(newBatchFiles.listBatches.total);
×
446
      setHasUploadingBatches(
×
447
        newBatchFiles.fullStatusList.batches.some((b) => b.status === "Uploading")
×
448
      );
449
    } catch (err) {
450
      setError("Unable to retrieve batch data.");
×
451
    } finally {
452
      setLoading(false);
×
453
    }
454
  };
455

456
  const updateSubmissionAction = async (action: SubmissionAction, reviewComment?: string) => {
×
457
    if (!submissionId) {
×
458
      return;
×
459
    }
460

461
    try {
×
462
      const { data: d, errors } = await submissionAction({
×
463
        variables: {
464
          submissionID: submissionId,
465
          action,
466
          comment: reviewComment,
467
        },
468
      });
469
      if (errors || !d?.submissionAction?._id) {
×
470
        throw new Error(`Error occurred while performing '${action}' submission action.`);
×
471
      }
472
      await getSubmission();
×
473
    } catch (err) {
474
      setError(err?.toString());
×
475
    }
476
  };
477

478
  const refreshBatchTable = () => {
×
479
    tableRef.current?.refresh();
×
480
  };
481

482
  const handleOnUpload = async (message: string, variant: VariantType) => {
×
483
    refreshBatchTable();
×
484
    enqueueSnackbar(message, { variant });
×
485

486
    const refreshStatuses: SubmissionStatus[] = ["New", "Withdrawn", "Rejected", "In Progress"];
×
487
    if (refreshStatuses.includes(data?.getSubmission?.status)) {
×
488
      await getSubmission();
×
489
    }
490
  };
491

492
  const handleCopyID = () => {
×
493
    if (!submissionId) {
×
494
      return;
×
495
    }
496
    navigator.clipboard.writeText(submissionId);
×
497
  };
498

499
  const handleOpenErrorDialog = (data: Batch) => {
×
500
    setOpenErrorDialog(true);
×
501
    setSelectedRow(data);
×
502
  };
503

504
  const handleOpenFileListDialog = (data: Batch) => {
×
505
    setOpenFileListDialog(true);
×
506
    setSelectedRow(data);
×
507
  };
508

509
  const handleOnValidate = (status: boolean) => {
×
510
    if (!status) {
×
511
      return;
×
512
    }
513

514
    // NOTE: Immediately update submission object to get "Validating" status
515
    getSubmission();
×
516
    startPolling(60000);
×
517
  };
518

519
  const providerValue = useMemo(
×
520
    () => ({
×
521
      handleOpenErrorDialog,
522
      handleOpenFileListDialog,
523
    }),
524
    [handleOpenErrorDialog]
525
  );
526

527
  useEffect(() => {
×
528
    if (!submissionId) {
×
529
      setError("Invalid submission ID provided.");
×
530
    } else if (submissionError) {
×
531
      setError("Unable to retrieve submission data.");
×
532
    }
533
  }, [submissionError]);
534

535
  useEffect(() => {
×
536
    if (
×
537
      data?.getSubmission?.fileValidationStatus !== "Validating" &&
×
538
      data?.getSubmission?.metadataValidationStatus !== "Validating"
539
    ) {
540
      stopPolling();
×
541
    } else {
542
      startPolling(60000);
×
543
    }
544
  }, [data?.getSubmission?.fileValidationStatus, data?.getSubmission?.metadataValidationStatus]);
545

546
  useEffect(() => {
×
547
    if (!hasUploadingBatches && batchRefreshTimeout) {
×
548
      clearInterval(batchRefreshTimeout);
×
549
      setBatchRefreshTimeout(null);
×
550
      getSubmission();
×
551
    } else if (!batchRefreshTimeout && hasUploadingBatches) {
×
552
      setBatchRefreshTimeout(setInterval(refreshBatchTable, 60000));
×
553
    }
554

555
    return () => clearInterval(batchRefreshTimeout);
×
556
  }, [hasUploadingBatches]);
557

558
  return (
×
559
    <StyledWrapper>
560
      <StyledBanner bannerSrc={bannerSvg} />
561
      <StyledBannerContentContainer maxWidth="xl">
562
        <StyledCopyWrapper direction="row" spacing={1.625} alignItems="center">
563
          <StyledCopyLabel id="data-submission-id-label" variant="body1">
564
            SUBMISSION ID:
565
          </StyledCopyLabel>
566
          <StyledCopyValue id="data-submission-id-value" variant="body1">
567
            {submissionId}
568
          </StyledCopyValue>
569
          {submissionId && (
×
570
            <StyledCopyIDButton
571
              id="data-submission-copy-id-button"
572
              onClick={handleCopyID}
573
              aria-label="Copy ID"
574
            >
575
              <CopyIconSvg />
576
            </StyledCopyIDButton>
577
          )}
578
        </StyledCopyWrapper>
579
        <StyledCard>
580
          <StyledCardContent>
581
            {error && <StyledAlert severity="error">Oops! An error occurred. {error}</StyledAlert>}
×
582
            <DataSubmissionSummary dataSubmission={data?.getSubmission} />
583
            <DataSubmissionStatistics
584
              dataSubmission={data?.getSubmission}
585
              statistics={data?.submissionStats?.stats}
586
            />
587
            <StyledFlowContainer>
588
              <UserGuide />
589
              <DataUpload
590
                submission={data?.getSubmission}
591
                readOnly={submissionLockedStatuses.includes(data?.getSubmission?.status)}
592
                onCreateBatch={refreshBatchTable}
593
                onUpload={handleOnUpload}
594
              />
595
              <ValidationControls
596
                dataSubmission={data?.getSubmission}
597
                onValidate={handleOnValidate}
598
              />
599
            </StyledFlowContainer>
600
            <StyledTabs value={isValidTab ? tab : URLTabs.DATA_ACTIVITY}>
×
601
              <LinkTab
602
                value={URLTabs.DATA_ACTIVITY}
603
                label="Data Activity"
604
                to={`/data-submission/${submissionId}/${URLTabs.DATA_ACTIVITY}`}
605
                selected={tab === URLTabs.DATA_ACTIVITY}
606
              />
607
              <LinkTab
608
                value={URLTabs.VALIDATION_RESULTS}
609
                label="Validation Results"
610
                to={`/data-submission/${submissionId}/${URLTabs.VALIDATION_RESULTS}`}
611
                selected={tab === URLTabs.VALIDATION_RESULTS}
612
              />
613
              <LinkTab
614
                value={URLTabs.SUBMITTED_DATA}
615
                label="Submitted Data"
616
                to={`/data-submission/${submissionId}/${URLTabs.SUBMITTED_DATA}`}
617
                selected={tab === URLTabs.SUBMITTED_DATA}
618
              />
619
            </StyledTabs>
620

621
            <StyledMainContentArea>
622
              {/* Primary Tab Content */}
623
              {tab === URLTabs.DATA_ACTIVITY && (
×
624
                <BatchTableContext.Provider value={providerValue}>
625
                  <GenericTable
626
                    ref={tableRef}
627
                    columns={columns}
628
                    data={batches || []}
×
629
                    total={totalBatches || 0}
×
630
                    loading={loading}
631
                    defaultRowsPerPage={20}
632
                    onFetchData={handleFetchBatches}
633
                    containerProps={{ sx: { marginBottom: "8px" } }}
634
                  />
635
                </BatchTableContext.Provider>
636
              )}
637
              {tab === URLTabs.VALIDATION_RESULTS && (
×
638
                <QualityControl submission={data?.getSubmission} />
639
              )}
640
              {tab === URLTabs.SUBMITTED_DATA && <SubmittedData submissionId={submissionId} />}
×
641

642
              {/* Return to Data Submission List Button */}
643
              <BackButton navigateTo="/data-submissions" text="Back to Data Submissions" />
644
            </StyledMainContentArea>
645
          </StyledCardContent>
646
          <StyledCardActions>
647
            <DataSubmissionActions
648
              submission={data?.getSubmission}
649
              onAction={updateSubmissionAction}
650
              submitActionButton={{
651
                disable: submitInfo?.disable,
652
                label: submitInfo?.isAdminOverride ? "Admin Submit" : "Submit",
×
653
              }}
654
              onError={(message: string) => enqueueSnackbar(message, { variant: "error" })}
×
655
            />
656
          </StyledCardActions>
657
        </StyledCard>
658
      </StyledBannerContentContainer>
659
      <ErrorDialog
660
        open={openErrorDialog}
661
        onClose={() => setOpenErrorDialog(false)}
×
662
        header="Data Submission"
663
        title={`Batch ${selectedRow?.displayID || ""} Upload Errors`}
×
664
        errors={selectedRow?.errors}
665
        uploadedDate={data?.getSubmission?.createdAt}
666
      />
667
      <FileListDialog
668
        open={openFileListDialog}
669
        batch={selectedRow}
670
        onClose={() => setOpenFileListDialog(false)}
×
671
      />
672
    </StyledWrapper>
673
  );
674
};
675

676
export default DataSubmission;
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