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

CBIIT / crdc-datahub-ui / 11823490147

13 Nov 2024 06:27PM UTC coverage: 53.812% (+0.3%) from 53.521%
11823490147

Pull #531

github

web-flow
Merge 1f17e425a into 56461c97e
Pull Request #531: Sync releases

2571 of 5273 branches covered (48.76%)

Branch coverage included in aggregate %.

43 of 60 new or added lines in 8 files covered. (71.67%)

1 existing line in 1 file now uncovered.

3689 of 6360 relevant lines covered (58.0%)

135.67 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"][0];
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: "Organization",
144
    renderValue: (a) => <TruncatedText text={a.organization.name} />,
×
145
    fieldKey: "organization.name",
146
  },
147
  {
148
    label: "Study",
149
    renderValue: (a) => <TruncatedText text={a.studyAbbreviation} />,
×
150
    field: "studyAbbreviation",
151
    hideable: false,
152
  },
153

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

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

177
  {
178
    label: "Node Count",
179
    renderValue: (a) =>
180
      Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(a.nodeCount || 0),
×
181
    field: "nodeCount",
182
  },
183
  {
184
    label: "Created Date",
185
    renderValue: (a) =>
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) =>
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
 */
223
const ListingView: FC = () => {
×
224
  usePageTitle("Data Submission List");
×
225

226
  const { state } = useLocation();
×
227
  const { status: authStatus } = useAuthContext();
×
228
  const { enqueueSnackbar } = useSnackbar();
×
229
  // Only org owners/submitters with organizations assigned can create data submissions
230

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

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

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

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

NEW
269
      if (!filtersRef.current) {
×
270
        return;
×
271
      }
272

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

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

302
      setData(d.listSubmissions.submissions);
×
NEW
303
      setOrganizations(
×
304
        d.listSubmissions.organizations
NEW
305
          ?.filter((org) => !!org.name.trim())
×
NEW
306
          ?.sort((a, b) => a.name?.localeCompare(b.name))
×
307
      );
308
      setSubmitterNames(d.listSubmissions.submitterNames?.filter((sn) => !!sn.trim()));
×
309
      setDataCommons(d.listSubmissions.dataCommons?.filter((dc) => !!dc.trim()));
×
310
      setTotalData(d.listSubmissions.total);
×
311
    } catch (err) {
312
      setError(true);
×
313
    } finally {
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 {
×
324
      setLoading(true);
×
325

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

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

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

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

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

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

421
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