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

CBIIT / crdc-datahub-ui / 12936221948

23 Jan 2025 07:03PM UTC coverage: 57.932% (-0.02%) from 57.952%
12936221948

Pull #605

github

web-flow
Merge 9c041f8e4 into bc7a90325
Pull Request #605: CRDCDH-2266 Support Viewing Older Data Model Versions

2807 of 5284 branches covered (53.12%)

Branch coverage included in aggregate %.

6 of 17 new or added lines in 6 files covered. (35.29%)

45 existing lines in 1 file now uncovered.

4033 of 6523 relevant lines covered (61.83%)

144.5 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
  FilterForm,
20
} from "../../components/DataSubmissions/DataSubmissionListFilters";
21

22
type T = ListSubmissionsResp["listSubmissions"]["submissions"][number];
23

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

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

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

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

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

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

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

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

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

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

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

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

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

UNCOV
226
  const { state } = useLocation();
×
UNCOV
227
  const { status: authStatus } = useAuthContext();
×
UNCOV
228
  const { enqueueSnackbar } = useSnackbar();
×
229

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

238
  const [loading, setLoading] = useState<boolean>(false);
×
239
  const [error, setError] = useState<boolean>(false);
×
UNCOV
240
  const [data, setData] = useState<T[]>([]);
×
241
  const [organizations, setOrganizations] = useState<Pick<Organization, "_id" | "name">[]>([]);
×
UNCOV
242
  const [submitterNames, setSubmitterNames] = useState<string[]>([]);
×
UNCOV
243
  const [dataCommons, setDataCommons] = useState<string[]>([]);
×
UNCOV
244
  const [totalData, setTotalData] = useState<number>(0);
×
245
  const tableRef = useRef<TableMethods>(null);
×
UNCOV
246
  const filtersRef = useRef<FilterForm>({
×
247
    organization: "All",
248
    status: "All",
249
    dataCommons: "All",
250
    name: "",
251
    dbGaPID: "",
252
    submitterName: "All",
253
  });
254

255
  const [listSubmissions, { refetch }] = useLazyQuery<ListSubmissionsResp, ListSubmissionsInput>(
×
256
    LIST_SUBMISSIONS,
257
    {
258
      context: { clientName: "backend" },
259
      fetchPolicy: "cache-and-network",
260
    }
261
  );
262

UNCOV
263
  const handleFetchData = async (fetchListing: FetchListing<T>, force: boolean) => {
×
UNCOV
264
    const { first, offset, sortDirection, orderBy } = fetchListing || {};
×
UNCOV
265
    try {
×
266
      setLoading(true);
×
267

UNCOV
268
      if (!filtersRef.current) {
×
UNCOV
269
        return;
×
270
      }
271

272
      const {
273
        organization,
274
        status,
275
        submitterName,
276
        name,
277
        dbGaPID,
278
        dataCommons: dc,
279
      } = filtersRef.current;
×
280

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

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

318
  const setTablePage = (page: number) => {
×
319
    tableRef.current?.setPage(page, true);
×
320
  };
321

322
  const handleOnCreateSubmission = async () => {
×
323
    try {
×
UNCOV
324
      setLoading(true);
×
325

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

UNCOV
346
    enqueueSnackbar("Data Submission Created Successfully", {
×
347
      variant: "success",
348
    });
349
  };
350

351
  const handleOnFiltersChange = (data: FilterForm) => {
×
352
    if (isEqual(data, filtersRef.current)) {
×
UNCOV
353
      return;
×
354
    }
355

UNCOV
356
    filtersRef.current = { ...data };
×
357
    setTablePage(0);
×
358
  };
359

UNCOV
360
  return (
×
361
    <>
362
      <PageBanner
363
        title="Data Submission List"
364
        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."
365
        padding="57px 0 0 25px"
366
        body={
367
          <StyledBannerBody direction="row" alignItems="center" justifyContent="flex-end">
368
            {/* NOTE For MVP-2: Organization Owners are just Users */}
369
            {/* Create a submission only available to org owners and submitters that have organizations assigned */}
370
            <CreateDataSubmissionDialog onCreate={handleOnCreateSubmission} />
371
          </StyledBannerBody>
372
        }
373
        bannerSrc={bannerSvg}
374
      />
375
      <StyledContainer maxWidth="xl">
376
        {(state?.error || error) && (
×
377
          <Alert sx={{ mb: 3, p: 2 }} severity="error">
378
            {state?.error || "An error occurred while loading the data."}
×
379
          </Alert>
380
        )}
381
        <StyledFilterTableWrapper>
382
          <DataSubmissionListFilters
383
            columns={columns}
384
            organizations={organizations}
385
            submitterNames={submitterNames}
386
            dataCommons={dataCommons}
387
            columnVisibilityModel={columnVisibilityModel}
388
            onColumnVisibilityModelChange={setColumnVisibilityModel}
389
            onChange={handleOnFiltersChange}
390
          />
391

392
          <GenericTable
393
            ref={tableRef}
394
            columns={visibleColumns}
395
            data={data || []}
×
396
            total={totalData || 0}
×
397
            loading={loading || authStatus === AuthStatus.LOADING}
×
398
            defaultRowsPerPage={20}
399
            defaultOrder="desc"
400
            disableUrlParams={false}
401
            position="bottom"
402
            noContentText="There are no data submissions associated with your account"
403
            onFetchData={handleFetchData}
404
            containerProps={{
405
              sx: {
406
                marginBottom: "8px",
407
                border: 0,
408
                borderTopLeftRadius: 0,
409
                borderTopRightRadius: 0,
410
              },
411
            }}
412
            CustomTableHead={StyledTableHead}
413
            CustomTableHeaderCell={StyledHeaderCell}
414
            CustomTableBodyCell={StyledTableCell}
415
          />
416
        </StyledFilterTableWrapper>
417
      </StyledContainer>
418
    </>
419
  );
420
};
421

422
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

© 2025 Coveralls, Inc