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

CBIIT / crdc-datahub-ui / 12282254806

11 Dec 2024 06:05PM UTC coverage: 56.341% (-0.09%) from 56.427%
12282254806

push

github

web-flow
Merge pull request #541 from CBIIT/CRDCDH-1991

CRDCDH-1991 Rebrand Organizations as Programs

2628 of 5151 branches covered (51.02%)

Branch coverage included in aggregate %.

2 of 15 new or added lines in 5 files covered. (13.33%)

5 existing lines in 2 files now uncovered.

3796 of 6251 relevant lines covered (60.73%)

142.48 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 } 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",
135
    renderValue: (a) => a.modelVersion,
×
136
    field: "modelVersion",
137
    hideable: true,
138
    sx: {
139
      width: "79px",
140
    },
141
  },
142
  {
143
    label: "Study",
144
    renderValue: (a) => <TruncatedText text={a.studyAbbreviation} />,
×
145
    field: "studyAbbreviation",
146
    hideable: false,
147
  },
148

149
  {
150
    label: "dbGaP ID",
151
    renderValue: (a) => <TruncatedText text={a.dbGaPID} maxCharacters={15} />,
×
152
    field: "dbGaPID",
153
    hideable: true,
154
  },
155

156
  {
157
    label: "Status",
158
    renderValue: (a) => a.status,
×
159
    field: "status",
160
    hideable: false,
161
    sx: {
162
      width: "87px",
163
    },
164
  },
165
  {
166
    label: "Primary Contact",
167
    renderValue: (a) => <TruncatedText text={a.conciergeName} />,
×
168
    field: "conciergeName",
169
    hideable: true,
170
  },
171

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

213
/**
214
 * View for List of Questionnaire/Submissions
215
 *
216
 * @returns {JSX.Element}
217
 */
218
const ListingView: FC = () => {
×
219
  usePageTitle("Data Submission List");
×
220

221
  const { state } = useLocation();
×
222
  const { status: authStatus } = useAuthContext();
×
223
  const { enqueueSnackbar } = useSnackbar();
×
224

UNCOV
225
  const { columnVisibilityModel, setColumnVisibilityModel, visibleColumns } = useColumnVisibility<
×
226
    Column<T>
227
  >({
228
    columns,
229
    getColumnKey: (c) => c.fieldKey ?? c.field,
×
230
    localStorageKey: "dataSubmissionListColumns",
231
  });
232

233
  const [loading, setLoading] = useState<boolean>(false);
×
234
  const [error, setError] = useState<boolean>(false);
×
235
  const [data, setData] = useState<T[]>([]);
×
236
  const [submitterNames, setSubmitterNames] = useState<string[]>([]);
×
237
  const [dataCommons, setDataCommons] = useState<string[]>([]);
×
238
  const [totalData, setTotalData] = useState<number>(0);
×
239
  const tableRef = useRef<TableMethods>(null);
×
240
  const filtersRef = useRef<FilterForm>({
×
241
    status: "All",
242
    dataCommons: "All",
243
    name: "",
244
    dbGaPID: "",
245
    submitterName: "All",
246
  });
247

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

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

261
      if (!filtersRef.current) {
×
262
        return;
×
263
      }
264

NEW
265
      const { status, submitterName, name, dbGaPID, dataCommons: dc } = filtersRef.current;
×
266

267
      const { data: d, error } = await listSubmissions({
×
268
        variables: {
269
          status: status ?? "All",
×
270
          dataCommons: dc ?? "All",
×
271
          submitterName: submitterName ?? "All",
×
272
          name: name || undefined,
×
273
          dbGaPID: dbGaPID || undefined,
×
274
          first,
275
          offset,
276
          sortDirection,
277
          orderBy,
278
        },
279
        context: { clientName: "backend" },
280
        fetchPolicy: "no-cache",
281
      });
282
      if (error || !d?.listSubmissions) {
×
283
        throw new Error("Unable to retrieve Data Submission List results.");
×
284
      }
285

286
      setData(d.listSubmissions.submissions);
×
287
      setSubmitterNames(d.listSubmissions.submitterNames?.filter((sn) => !!sn.trim()));
×
UNCOV
288
      setDataCommons(d.listSubmissions.dataCommons?.filter((dc) => !!dc.trim()));
×
289
      setTotalData(d.listSubmissions.total);
×
290
    } catch (err) {
291
      setError(true);
×
292
    } finally {
293
      setLoading(false);
×
294
    }
295
  };
296

297
  const setTablePage = (page: number) => {
×
298
    tableRef.current?.setPage(page, true);
×
299
  };
300

301
  const handleOnCreateSubmission = async () => {
×
302
    try {
×
303
      setLoading(true);
×
304

305
      const { data: d } = await refetch();
×
306
      if (error || !d?.listSubmissions) {
×
307
        throw new Error("Unable to retrieve Data Submission List results.");
×
308
      }
309
      setData(d.listSubmissions.submissions);
×
310
      setSubmitterNames(d.listSubmissions.submitterNames?.filter((sn) => !!sn.trim()));
×
UNCOV
311
      setDataCommons(d.listSubmissions.dataCommons?.filter((dc) => !!dc.trim()));
×
312
      setTotalData(d.listSubmissions.total);
×
313
    } catch (err) {
314
      setError(true);
×
315
    } finally {
316
      setLoading(false);
×
317
    }
318

319
    enqueueSnackbar("Data Submission Created Successfully", {
×
320
      variant: "success",
321
    });
322
  };
323

324
  const handleOnFiltersChange = (data: FilterForm) => {
×
325
    if (isEqual(data, filtersRef.current)) {
×
326
      return;
×
327
    }
328

329
    filtersRef.current = { ...data };
×
330
    setTablePage(0);
×
331
  };
332

333
  return (
×
334
    <>
335
      <PageBanner
336
        title="Data Submission List"
337
        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."
338
        padding="57px 0 0 25px"
339
        body={
340
          <StyledBannerBody direction="row" alignItems="center" justifyContent="flex-end">
341
            {/* NOTE For MVP-2: Organization Owners are just Users */}
342
            {/* Create a submission only available to org owners and submitters that have organizations assigned */}
343
            <CreateDataSubmissionDialog onCreate={handleOnCreateSubmission} />
344
          </StyledBannerBody>
345
        }
346
        bannerSrc={bannerSvg}
347
      />
348
      <StyledContainer maxWidth="xl">
349
        {(state?.error || error) && (
×
350
          <Alert sx={{ mb: 3, p: 2 }} severity="error">
351
            {state?.error || "An error occurred while loading the data."}
×
352
          </Alert>
353
        )}
354
        <StyledFilterTableWrapper>
355
          <DataSubmissionListFilters
356
            columns={columns}
357
            submitterNames={submitterNames}
358
            dataCommons={dataCommons}
359
            columnVisibilityModel={columnVisibilityModel}
360
            onColumnVisibilityModelChange={setColumnVisibilityModel}
361
            onChange={handleOnFiltersChange}
362
          />
363

364
          <GenericTable
365
            ref={tableRef}
366
            columns={visibleColumns}
367
            data={data || []}
×
368
            total={totalData || 0}
×
369
            loading={loading || authStatus === AuthStatus.LOADING}
×
370
            defaultRowsPerPage={20}
371
            defaultOrder="desc"
372
            disableUrlParams={false}
373
            position="bottom"
374
            noContentText="There are no data submissions associated with your account"
375
            onFetchData={handleFetchData}
376
            containerProps={{
377
              sx: {
378
                marginBottom: "8px",
379
                border: 0,
380
                borderTopLeftRadius: 0,
381
                borderTopRightRadius: 0,
382
              },
383
            }}
384
            CustomTableHead={StyledTableHead}
385
            CustomTableHeaderCell={StyledHeaderCell}
386
            CustomTableBodyCell={StyledTableCell}
387
          />
388
        </StyledFilterTableWrapper>
389
      </StyledContainer>
390
    </>
391
  );
392
};
393

394
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