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

CBIIT / crdc-datahub-ui / 13115827658

03 Feb 2025 02:33PM UTC coverage: 58.177% (-0.02%) from 58.194%
13115827658

Pull #606

github

web-flow
Merge 368abe02b into 322ce55c3
Pull Request #606: CRDCDH-2265 Support multi-select status selection

2840 of 5319 branches covered (53.39%)

Branch coverage included in aggregate %.

13 of 15 new or added lines in 3 files covered. (86.67%)

2 existing lines in 1 file now uncovered.

4061 of 6543 relevant lines covered (62.07%)

144.09 hits per line

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

0.0
/src/content/dataSubmissions/DataSubmissionsListView.tsx
1
import React, { FC, useRef, useState } from "react";
2
import { Link, useLocation } from "react-router-dom";
3
import { Alert, Container, Stack, styled, TableCell, TableHead, Box } from "@mui/material";
4
import { isEqual } from "lodash";
5
import { useSnackbar } from "notistack";
6
import { useLazyQuery } from "@apollo/client";
7
import bannerSvg from "../../assets/banner/submission_banner.png";
8
import PageBanner from "../../components/PageBanner";
9
import { FormatDate, Logger } from "../../utils";
10
import { useAuthContext, Status as AuthStatus } from "../../components/Contexts/AuthContext";
11
import usePageTitle from "../../hooks/usePageTitle";
12
import CreateDataSubmissionDialog from "../../components/DataSubmissions/CreateDataSubmissionDialog";
13
import GenericTable, { Column } from "../../components/GenericTable";
14
import { LIST_SUBMISSIONS, ListSubmissionsInput, ListSubmissionsResp } from "../../graphql";
15
import TruncatedText from "../../components/TruncatedText";
16
import StyledTooltip from "../../components/StyledFormComponents/StyledTooltip";
17
import { useColumnVisibility } from "../../hooks/useColumnVisibility";
18
import DataSubmissionListFilters, {
19
  defaultValues,
20
  FilterForm,
21
} from "../../components/DataSubmissions/DataSubmissionListFilters";
22
import NavigatorLink from "../../components/DataSubmissions/NavigatorLink";
23

24
type T = ListSubmissionsResp["listSubmissions"]["submissions"][number];
25

26
const StyledBannerBody = styled(Stack)({
×
27
  marginTop: "-20px",
28
});
29

30
const StyledContainer = styled(Container)({
×
31
  marginTop: "-62px",
32
});
33

34
const StyledTableHead = styled(TableHead)({
×
35
  background: "#083A50",
36
  borderTop: "1px solid #6B7294",
37
  borderBottom: "1px solid #6B7294",
38
});
39

40
const StyledHeaderCell = styled(TableCell)({
×
41
  fontWeight: 700,
42
  fontSize: "14px",
43
  lineHeight: "16px",
44
  color: "#fff !important",
45
  "&.MuiTableCell-root": {
46
    padding: "15px 4px 17px",
47
    color: "#fff !important",
48
    // whiteSpace: "nowrap",
49
  },
50
  "& .MuiSvgIcon-root, & .MuiButtonBase-root": {
51
    color: "#fff !important",
52
  },
53
  "& .MuiSvgIcon-root": {
54
    marginRight: 0,
55
  },
56
  "&:last-of-type": {
57
    paddingRight: "4px",
58
  },
59
});
60

61
const StyledTableCell = styled(TableCell)({
×
62
  fontSize: "14px",
63
  color: "#083A50 !important",
64
  "&.MuiTableCell-root": {
65
    padding: "14px 4px 12px",
66
    overflowWrap: "anywhere",
67
    whiteSpace: "nowrap",
68
  },
69
  "&:last-of-type": {
70
    paddingRight: "4px",
71
  },
72
});
73

74
const StyledFilterTableWrapper = styled(Box)({
×
75
  borderRadius: "8px",
76
  background: "#FFF",
77
  border: "1px solid #6CACDA",
78
  marginBottom: "25px",
79
});
80

81
const StyledDisabledText = styled(Box)(({ theme }) => ({
×
82
  color: theme.palette.text.disabled,
83
}));
84

85
const StyledDateTooltip = styled(StyledTooltip)(() => ({
×
86
  cursor: "pointer",
87
}));
88

89
const columns: Column<T>[] = [
×
90
  {
91
    label: "Submission Name",
92
    renderValue: (a) =>
93
      a.status === "Deleted" || a.archived === true ? (
×
94
        <StyledDisabledText>
95
          <TruncatedText text={a.name} />
96
        </StyledDisabledText>
97
      ) : (
98
        <Link to={`/data-submission/${a._id}/upload-activity`}>
99
          <TruncatedText text={a.name} underline={false} />
100
        </Link>
101
      ),
102
    field: "name",
103
    hideable: false,
104
    sx: {
105
      width: "139px",
106
    },
107
  },
108
  {
109
    label: "Submitter",
110
    renderValue: (a) => <TruncatedText text={a.submitterName} />,
×
111
    field: "submitterName",
112
    hideable: true,
113
    sx: {
114
      width: "102px",
115
    },
116
  },
117
  {
118
    label: "Data Commons",
119
    renderValue: (a) => a.dataCommons,
×
120
    field: "dataCommons",
121
    hideable: true,
122
    sx: {
123
      width: "94px",
124
    },
125
  },
126
  {
127
    label: "Type",
128
    renderValue: (a) => a.intention,
×
129
    field: "intention",
130
    hideable: true,
131
    sx: {
132
      width: "96px",
133
    },
134
  },
135
  {
136
    label: "DM Version",
137
    renderValue: (a) => <NavigatorLink submission={a} />,
×
138
    field: "modelVersion",
139
    hideable: true,
140
    sx: {
141
      width: "79px",
142
    },
143
  },
144
  {
145
    label: "Program",
146
    renderValue: (a) => <TruncatedText text={a.organization?.name ?? "NA"} />,
×
147
    fieldKey: "organization.name",
148
  },
149
  {
150
    label: "Study",
151
    renderValue: (a) => <TruncatedText text={a.studyAbbreviation} />,
×
152
    field: "studyAbbreviation",
153
    hideable: false,
154
  },
155

156
  {
157
    label: "dbGaP ID",
158
    renderValue: (a) => <TruncatedText text={a.dbGaPID} maxCharacters={15} />,
×
159
    field: "dbGaPID",
160
    hideable: true,
161
  },
162

163
  {
164
    label: "Status",
165
    renderValue: (a) => a.status,
×
166
    field: "status",
167
    hideable: false,
168
    sx: {
169
      width: "87px",
170
    },
171
  },
172
  {
173
    label: "Primary Contact",
174
    renderValue: (a) => <TruncatedText text={a.conciergeName} />,
×
175
    field: "conciergeName",
176
    hideable: true,
177
  },
178

179
  {
180
    label: "Record Count",
181
    renderValue: (a) =>
182
      Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(a.nodeCount || 0),
×
183
    field: "nodeCount",
184
  },
185
  {
186
    label: "Created Date",
187
    renderValue: (a) =>
188
      a.createdAt ? (
×
189
        <StyledDateTooltip title={FormatDate(a.createdAt, "M/D/YYYY h:mm A")} placement="top">
190
          <span>{FormatDate(a.createdAt, "M/D/YYYY")}</span>
191
        </StyledDateTooltip>
192
      ) : (
193
        ""
194
      ),
195
    field: "createdAt",
196
    hideable: true,
197
    sx: {
198
      width: "92px",
199
    },
200
  },
201
  {
202
    label: "Last Updated",
203
    renderValue: (a) =>
204
      a.updatedAt ? (
×
205
        <StyledDateTooltip title={FormatDate(a.updatedAt, "M/D/YYYY h:mm A")} placement="top">
206
          <span>{FormatDate(a.updatedAt, "M/D/YYYY")}</span>
207
        </StyledDateTooltip>
208
      ) : (
209
        ""
210
      ),
211
    field: "updatedAt",
212
    hideable: true,
213
    default: true,
214
    sx: {
215
      width: "108px",
216
    },
217
  },
218
];
219

220
/**
221
 * View for List of Questionnaire/Submissions
222
 *
223
 * @returns {JSX.Element}
224
 */
225
const ListingView: FC = () => {
×
226
  usePageTitle("Data Submission List");
×
227

228
  const { state } = useLocation();
×
229
  const { status: authStatus } = useAuthContext();
×
230
  const { enqueueSnackbar } = useSnackbar();
×
231

232
  const { columnVisibilityModel, setColumnVisibilityModel, visibleColumns } = useColumnVisibility<
×
233
    Column<T>
234
  >({
235
    columns,
236
    getColumnKey: (c) => c.fieldKey ?? c.field,
×
237
    localStorageKey: "dataSubmissionListColumns",
238
  });
239

240
  const [loading, setLoading] = useState<boolean>(false);
×
241
  const [error, setError] = useState<boolean>(false);
×
242
  const [data, setData] = useState<T[]>([]);
×
243
  const [organizations, setOrganizations] = useState<Pick<Organization, "_id" | "name">[]>([]);
×
244
  const [submitterNames, setSubmitterNames] = useState<string[]>([]);
×
245
  const [dataCommons, setDataCommons] = useState<string[]>([]);
×
246
  const [totalData, setTotalData] = useState<number>(0);
×
247
  const tableRef = useRef<TableMethods>(null);
×
NEW
248
  const filtersRef = useRef<FilterForm>({ ...defaultValues });
×
249

250
  const [listSubmissions, { refetch }] = useLazyQuery<ListSubmissionsResp, ListSubmissionsInput>(
×
251
    LIST_SUBMISSIONS,
252
    {
253
      context: { clientName: "backend" },
254
      fetchPolicy: "cache-and-network",
255
    }
256
  );
257

258
  const handleFetchData = async (fetchListing: FetchListing<T>, force: boolean) => {
×
259
    const { first, offset, sortDirection, orderBy } = fetchListing || {};
×
260
    try {
×
261
      setLoading(true);
×
262

263
      if (!filtersRef.current) {
×
264
        return;
×
265
      }
266

267
      const {
268
        organization,
269
        status,
270
        submitterName,
271
        name,
272
        dbGaPID,
273
        dataCommons: dc,
274
      } = filtersRef.current;
×
275

276
      const { data: d, error } = await listSubmissions({
×
277
        variables: {
278
          organization: organization ?? "All",
×
279
          status,
280
          dataCommons: dc ?? "All",
×
281
          submitterName: submitterName ?? "All",
×
282
          name: name || undefined,
×
283
          dbGaPID: dbGaPID || undefined,
×
284
          first,
285
          offset,
286
          sortDirection,
287
          orderBy,
288
        },
289
        context: { clientName: "backend" },
290
        fetchPolicy: "no-cache",
291
      });
292
      if (error || !d?.listSubmissions) {
×
293
        throw new Error("Unable to retrieve Data Submission List results.");
×
294
      }
295

296
      setData(d.listSubmissions.submissions);
×
297
      setOrganizations(
×
298
        d.listSubmissions.organizations
299
          ?.filter((org) => !!org?.name?.trim())
×
300
          ?.sort((a, b) => a.name?.localeCompare(b?.name))
×
301
      );
302
      setSubmitterNames(d.listSubmissions.submitterNames?.filter((sn) => !!sn.trim()));
×
303
      setDataCommons(d.listSubmissions.dataCommons?.filter((dc) => !!dc.trim()));
×
304
      setTotalData(d.listSubmissions.total);
×
305
    } catch (err) {
306
      Logger.error("Error while fetching Data Submission list", err);
×
307
      setError(true);
×
308
    } finally {
309
      setLoading(false);
×
310
    }
311
  };
312

313
  const setTablePage = (page: number) => {
×
314
    tableRef.current?.setPage(page, true);
×
315
  };
316

317
  const handleOnCreateSubmission = async () => {
×
318
    try {
×
319
      setLoading(true);
×
320

321
      const { data: d } = await refetch();
×
322
      if (error || !d?.listSubmissions) {
×
323
        throw new Error("Unable to retrieve Data Submission List results.");
×
324
      }
325
      setData(d.listSubmissions.submissions);
×
326
      setOrganizations(
×
327
        d.listSubmissions.organizations
328
          ?.filter((org) => !!org?.name?.trim())
×
329
          ?.sort((a, b) => a.name?.localeCompare(b?.name))
×
330
      );
331
      setSubmitterNames(d.listSubmissions.submitterNames?.filter((sn) => !!sn.trim()));
×
332
      setDataCommons(d.listSubmissions.dataCommons?.filter((dc) => !!dc.trim()));
×
333
      setTotalData(d.listSubmissions.total);
×
334
    } catch (err) {
335
      Logger.error("Error updating the Data Submission list", err);
×
336
      setError(true);
×
337
    } finally {
338
      setLoading(false);
×
339
    }
340

341
    enqueueSnackbar("Data Submission Created Successfully", {
×
342
      variant: "success",
343
    });
344
  };
345

346
  const handleOnFiltersChange = (data: FilterForm) => {
×
347
    if (isEqual(data, filtersRef.current)) {
×
348
      return;
×
349
    }
350

351
    filtersRef.current = { ...data };
×
352
    setTablePage(0);
×
353
  };
354

355
  return (
×
356
    <>
357
      <PageBanner
358
        title="Data Submission List"
359
        subTitle="Below is a list of data submissions that are associated with your account. Please click on any of the data submissions to review or continue work."
360
        padding="57px 0 0 25px"
361
        body={
362
          <StyledBannerBody direction="row" alignItems="center" justifyContent="flex-end">
363
            <CreateDataSubmissionDialog onCreate={handleOnCreateSubmission} />
364
          </StyledBannerBody>
365
        }
366
        bannerSrc={bannerSvg}
367
      />
368
      <StyledContainer maxWidth="xl">
369
        {(state?.error || error) && (
×
370
          <Alert sx={{ mb: 3, p: 2 }} severity="error">
371
            {state?.error || "An error occurred while loading the data."}
×
372
          </Alert>
373
        )}
374
        <StyledFilterTableWrapper>
375
          <DataSubmissionListFilters
376
            columns={columns}
377
            organizations={organizations}
378
            submitterNames={submitterNames}
379
            dataCommons={dataCommons}
380
            columnVisibilityModel={columnVisibilityModel}
381
            onColumnVisibilityModelChange={setColumnVisibilityModel}
382
            onChange={handleOnFiltersChange}
383
          />
384

385
          <GenericTable
386
            ref={tableRef}
387
            columns={visibleColumns}
388
            data={data || []}
×
389
            total={totalData || 0}
×
390
            loading={loading || authStatus === AuthStatus.LOADING}
×
391
            defaultRowsPerPage={20}
392
            defaultOrder="desc"
393
            disableUrlParams={false}
394
            position="bottom"
395
            noContentText="You either do not have the appropriate permissions to view data submissions, or there are no data submissions associated with your account."
396
            onFetchData={handleFetchData}
397
            containerProps={{
398
              sx: {
399
                marginBottom: "8px",
400
                border: 0,
401
                borderTopLeftRadius: 0,
402
                borderTopRightRadius: 0,
403
              },
404
            }}
405
            CustomTableHead={StyledTableHead}
406
            CustomTableHeaderCell={StyledHeaderCell}
407
            CustomTableBodyCell={StyledTableCell}
408
          />
409
        </StyledFilterTableWrapper>
410
      </StyledContainer>
411
    </>
412
  );
413
};
414

415
export default ListingView;
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