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

CBIIT / crdc-datahub-ui / 9745313718

01 Jul 2024 01:38PM UTC coverage: 36.609% (+1.9%) from 34.674%
9745313718

push

github

web-flow
Merge pull request #412 from CBIIT/CRDCDH-1240

CRDCDH-1240 Inconsistent Submission/Batch Polling

1259 of 4037 branches covered (31.19%)

Branch coverage included in aggregate %.

72 of 106 new or added lines in 5 files covered. (67.92%)

2 existing lines in 1 file now uncovered.

1982 of 4816 relevant lines covered (41.15%)

97.67 hits per line

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

60.96
/src/content/dataSubmissions/DataActivity.tsx
1
import React, {
2
  forwardRef,
3
  useCallback,
4
  useEffect,
5
  useImperativeHandle,
6
  useMemo,
7
  useRef,
8
  useState,
9
} from "react";
10
import { useLazyQuery } from "@apollo/client";
11
import { isEqual } from "lodash";
12
import { Box, Button, styled } from "@mui/material";
13
import { useSnackbar } from "notistack";
14
import { LIST_BATCHES, ListBatchesResp } from "../../graphql";
15
import GenericTable, { Column } from "../../components/GenericTable";
16
import BatchTableContext from "./Contexts/BatchTableContext";
17
import { useSubmissionContext } from "../../components/Contexts/SubmissionContext";
18
import { FormatDate } from "../../utils";
19
import FileListDialog from "../../components/FileListDialog";
20
import ErrorDetailsDialog from "../../components/ErrorDetailsDialog";
21

22
const StyledRejectedStatus = styled("div")({
2✔
23
  color: "#B54717",
24
  fontWeight: 600,
25
});
26

27
const StyledFileCountButton = styled(Button)({
2✔
28
  color: "#0B6CB1",
29
  fontFamily: "Inter",
30
  fontSize: "16px",
31
  fontStyle: "normal",
32
  fontWeight: 600,
33
  lineHeight: "19px",
34
  textDecorationLine: "underline",
35
  textTransform: "none",
36
  padding: 0,
37
  justifyContent: "flex-start",
38
  "&:hover": {
39
    background: "transparent",
40
    textDecorationLine: "underline",
41
  },
42
});
43

44
const StyledErrorDetailsButton = styled(Button)({
2✔
45
  color: "#0B6CB1",
46
  fontFamily: "Inter",
47
  fontSize: "16px",
48
  fontStyle: "normal",
49
  fontWeight: 600,
50
  lineHeight: "19px",
51
  textDecorationLine: "underline",
52
  textTransform: "none",
53
  padding: 0,
54
  justifyContent: "flex-start",
55
  "&:hover": {
56
    background: "transparent",
57
    textDecorationLine: "underline",
58
  },
59
});
60

61
const columns: Column<Batch>[] = [
2✔
62
  {
63
    label: "Batch ID",
NEW
64
    renderValue: (data) => data.displayID,
×
65
    field: "displayID",
66
  },
67
  {
68
    label: "Upload Type",
NEW
69
    renderValue: (data) => (data?.type !== "metadata" ? "-" : data?.metadataIntention),
×
70
    field: "metadataIntention",
71
  },
72
  {
73
    label: "Batch Type",
NEW
74
    renderValue: (data) => <Box textTransform="capitalize">{data?.type}</Box>,
×
75
    field: "type",
76
  },
77
  {
78
    label: "File Count",
79
    renderValue: (data) => (
NEW
80
      <BatchTableContext.Consumer>
×
81
        {({ handleOpenFileListDialog }) => (
NEW
82
          <StyledFileCountButton
×
83
            data-testid={`activity-file-count-${data?._id}`}
NEW
84
            onClick={() => handleOpenFileListDialog && handleOpenFileListDialog(data)}
×
85
            variant="text"
86
            disableRipple
87
            disableTouchRipple
88
            disableFocusRipple
89
          >
90
            {Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(data?.fileCount || 0)}
×
91
          </StyledFileCountButton>
92
        )}
93
      </BatchTableContext.Consumer>
94
    ),
95
    field: "fileCount",
96
  },
97
  {
98
    label: "Status",
99
    renderValue: (data) => (
NEW
100
      <Box textTransform="capitalize">
×
101
        {data.status === "Failed" ? (
×
102
          <StyledRejectedStatus>{data.status}</StyledRejectedStatus>
103
        ) : (
104
          data.status
105
        )}
106
      </Box>
107
    ),
108
    field: "status",
109
  },
110
  {
111
    label: "Uploaded Date",
112
    renderValue: (data) =>
NEW
113
      data?.createdAt ? `${FormatDate(data.createdAt, "MM-DD-YYYY [at] hh:mm A")}` : "",
×
114
    field: "createdAt",
115
    default: true,
116
    sx: {
117
      minWidth: "240px",
118
    },
119
  },
120
  {
121
    label: "Upload Errors",
122
    renderValue: (data) => (
NEW
123
      <BatchTableContext.Consumer>
×
124
        {({ handleOpenErrorDialog }) => {
NEW
125
          if (!data?.errors?.length || !handleOpenErrorDialog) {
×
NEW
126
            return null;
×
127
          }
128

NEW
129
          return (
×
130
            <StyledErrorDetailsButton
NEW
131
              onClick={() => handleOpenErrorDialog && handleOpenErrorDialog(data)}
×
132
              data-testid={`activity-error-count-${data?._id}`}
133
              variant="text"
134
              disableRipple
135
              disableTouchRipple
136
              disableFocusRipple
137
            >
138
              {`${data.errors.length} ${data.errors.length === 1 ? "Error" : "Errors"}`}
×
139
            </StyledErrorDetailsButton>
140
          );
141
        }}
142
      </BatchTableContext.Consumer>
143
    ),
144
    field: "errors",
145
    sortDisabled: true,
146
  },
147
];
148

149
export type DataActivityRef = {
150
  /**
151
   * A reference to the nest table methods.
152
   */
153
  tableRef: TableMethods;
154
};
155

156
const DataActivity = forwardRef<DataActivityRef>((_, ref) => {
2✔
157
  const { enqueueSnackbar } = useSnackbar();
44✔
158
  const {
159
    data: dataSubmission,
160
    refetch: getSubmission,
161
    isPolling: isSubmissionPolling,
162
  } = useSubmissionContext();
44✔
163
  const { _id: submissionId } = dataSubmission?.getSubmission || {};
44✔
164

165
  const [loading, setLoading] = useState<boolean>(false);
44✔
166
  const [data, setData] = useState<Batch[]>([]);
44✔
167
  const [prevData, setPrevData] = useState<FetchListing<Batch>>(null);
44✔
168
  const [totalData, setTotalData] = useState<number>(0);
44✔
169
  const [hasUploadingBatches, setHasUploadingBatches] = useState<boolean>(false);
44✔
170
  const [openErrorDialog, setOpenErrorDialog] = useState<boolean>(false);
44✔
171
  const [openFileListDialog, setOpenFileListDialog] = useState<boolean>(false);
44✔
172
  const [selectedRow, setSelectedRow] = useState<Batch | null>(null);
44✔
173
  const [startPollingFn, setPollingFn] = useState<((pollInterval: number) => void) | null>(null);
44✔
174
  const [stopPollingFn, setStopPollingFn] = useState<(() => void) | null>(null);
44✔
175

176
  const tableRef = useRef<TableMethods>(null);
44✔
177

178
  const [listBatches] = useLazyQuery<ListBatchesResp>(LIST_BATCHES, {
44✔
179
    notifyOnNetworkStatusChange: true,
180
    onCompleted: (data: ListBatchesResp) => {
181
      setData(data.listBatches.batches);
2✔
182
      setTotalData(data.listBatches.total);
2✔
183
      setHasUploadingBatches(data.fullStatusList.batches.some((b) => b.status === "Uploading"));
2✔
184
    },
185
    context: { clientName: "backend" },
186
    fetchPolicy: "cache-and-network",
187
  });
188

189
  const handleOpenErrorDialog = useCallback(
44✔
190
    (data: Batch) => {
NEW
191
      setOpenErrorDialog(true);
×
NEW
192
      setSelectedRow(data);
×
193
    },
194
    [setOpenErrorDialog, setSelectedRow]
195
  );
196

197
  const handleOpenFileListDialog = useCallback(
44✔
198
    (data: Batch) => {
NEW
199
      setOpenFileListDialog(true);
×
NEW
200
      setSelectedRow(data);
×
201
    },
202
    [setOpenFileListDialog, setSelectedRow]
203
  );
204

205
  const handleFetchBatches = useCallback(
44✔
206
    async (fetchListing: FetchListing<Batch>, force: boolean) => {
207
      const { first, offset, sortDirection, orderBy } = fetchListing || {};
16!
208
      if (!submissionId) {
16✔
209
        return;
8✔
210
      }
211
      if (!force && data?.length > 0 && isEqual(fetchListing, prevData)) {
8!
NEW
212
        return;
×
213
      }
214

215
      setPrevData(fetchListing);
8✔
216

217
      try {
8✔
218
        setLoading(true);
8✔
219
        const {
220
          data: newBatchFiles,
221
          error: batchFilesError,
222
          startPolling,
223
          stopPolling,
224
        } = await listBatches({
8✔
225
          variables: {
226
            submissionID: submissionId,
227
            first,
228
            offset,
229
            sortDirection,
230
            orderBy,
231
          },
232
        });
233

234
        if (batchFilesError || !newBatchFiles?.listBatches) {
6✔
235
          throw new Error("Unable to retrieve batch data.");
4✔
236
        }
237

238
        const hasUploading = newBatchFiles.fullStatusList?.batches?.some(
2✔
239
          (b) => b.status === "Uploading"
2✔
240
        );
241

242
        if (hasUploading) {
2!
243
          setPollingFn(() => startPolling);
2✔
244
          setStopPollingFn(() => stopPolling);
2✔
245
        }
246
      } catch (err) {
247
        enqueueSnackbar("Unable to retrieve batch data.", { variant: "error" });
4✔
248
      } finally {
249
        setLoading(false);
6✔
250
      }
251
    },
252
    [submissionId, data.length, prevData, listBatches, enqueueSnackbar, setLoading]
253
  );
254

255
  const batchContextValue = useMemo(
44✔
256
    () => ({
16✔
257
      handleOpenErrorDialog,
258
      handleOpenFileListDialog,
259
    }),
260
    [handleOpenErrorDialog, handleOpenFileListDialog]
261
  );
262

263
  useImperativeHandle(
44✔
264
    ref,
NEW
265
    () => ({
×
266
      tableRef: tableRef.current,
267
    }),
268
    [tableRef.current]
269
  );
270

271
  useEffect(() => {
44✔
272
    if (!hasUploadingBatches && stopPollingFn) {
18!
NEW
273
      stopPollingFn();
×
274

NEW
275
      setPollingFn(null);
×
NEW
276
      setStopPollingFn(null);
×
NEW
277
      return;
×
278
    }
279
    if (hasUploadingBatches && startPollingFn) {
18✔
280
      startPollingFn(1000);
2✔
281

282
      // If the submission is not polling, refetch to kick-off polling
283
      if (!isSubmissionPolling) {
2!
284
        getSubmission();
2✔
285
      }
286
    }
287
  }, [hasUploadingBatches, stopPollingFn, startPollingFn]);
288

289
  useEffect(() => {
44✔
290
    tableRef?.current?.refresh();
16✔
291
  }, [submissionId]);
292

293
  return (
44✔
294
    <>
295
      <BatchTableContext.Provider value={batchContextValue}>
296
        <GenericTable
297
          ref={tableRef}
298
          columns={columns}
299
          data={data || []}
22!
300
          total={totalData || 0}
44✔
301
          loading={loading}
302
          defaultRowsPerPage={20}
303
          onFetchData={handleFetchBatches}
304
          containerProps={{ sx: { marginBottom: "8px" } }}
305
        />
306
      </BatchTableContext.Provider>
307
      <ErrorDetailsDialog
308
        open={openErrorDialog}
NEW
309
        onClose={() => setOpenErrorDialog(false)}
×
310
        header="Data Submission"
311
        title={`Batch ${selectedRow?.displayID || ""} Upload Errors`}
44✔
312
        errors={selectedRow?.errors}
313
        uploadedDate={dataSubmission?.getSubmission?.createdAt}
314
      />
315
      <FileListDialog
316
        open={openFileListDialog}
317
        batch={selectedRow}
NEW
318
        onClose={() => setOpenFileListDialog(false)}
×
319
      />
320
    </>
321
  );
322
});
323

NEW
324
export default React.memo(DataActivity, (prevProps, nextProps) => isEqual(prevProps, nextProps));
×
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