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

CBIIT / crdc-datahub-ui / 16421677142

21 Jul 2025 03:47PM UTC coverage: 73.687% (+2.1%) from 71.57%
16421677142

Pull #787

github

web-flow
Merge 1ebb35bac into d93ea77f5
Pull Request #787: CRDCDH-2991 OMB Banner Dynamic Details

3844 of 4256 branches covered (90.32%)

Branch coverage included in aggregate %.

72 of 72 new or added lines in 3 files covered. (100.0%)

83 existing lines in 4 files now uncovered.

24583 of 34322 relevant lines covered (71.62%)

115.54 hits per line

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

93.55
/src/content/studies/ListView.tsx
1
import { useLazyQuery } from "@apollo/client";
1✔
2
import { Alert, Box, Button, Container, Stack, styled, TableCell, TableHead } from "@mui/material";
1✔
3
import { ElementType, useRef, useState } from "react";
1✔
4
import { Link, LinkProps, useLocation } from "react-router-dom";
1✔
5

6
import TooltipList from "@/components/SummaryList/TooltipList";
1✔
7

8
import ApprovedStudyFilters, {
1✔
9
  FilterForm,
10
} from "../../components/AdminPortal/Studies/ApprovedStudyFilters";
11
import GenericTable, { Column } from "../../components/GenericTable";
1✔
12
import PageBanner from "../../components/PageBanner";
1✔
13
import Asterisk from "../../components/StyledFormComponents/StyledAsterisk";
1✔
14
import StyledTooltip from "../../components/StyledFormComponents/StyledTooltip";
1✔
15
import SummaryList from "../../components/SummaryList";
1✔
16
import TruncatedText from "../../components/TruncatedText";
1✔
17
import {
1✔
18
  LIST_APPROVED_STUDIES,
19
  ListApprovedStudiesInput,
20
  ListApprovedStudiesResp,
21
} from "../../graphql";
22
import usePageTitle from "../../hooks/usePageTitle";
1✔
23
import { FormatDate } from "../../utils";
1✔
24
import { formatAccessTypes } from "../../utils/studyUtils";
1✔
25

26
const StyledButton = styled(Button)<{ component: ElementType } & LinkProps>({
1✔
27
  padding: "14px 20px",
1✔
28
  fontWeight: 700,
1✔
29
  fontSize: "16px",
1✔
30
  fontFamily: "'Nunito', 'Rubik', sans-serif",
1✔
31
  letterSpacing: "2%",
1✔
32
  lineHeight: "20.14px",
1✔
33
  borderRadius: "8px",
1✔
34
  color: "#fff",
1✔
35
  textTransform: "none",
1✔
36
  borderColor: "#26B893 !important",
1✔
37
  background: "#1B8369 !important",
1✔
38
  marginRight: "0",
1✔
39
});
1✔
40

41
const StyledBannerBody = styled(Stack)({
1✔
42
  marginTop: "-53px",
1✔
43
});
1✔
44

45
const StyledContainer = styled(Container)({
1✔
46
  marginTop: "-180px",
1✔
47
  paddingBottom: "90px",
1✔
48
});
1✔
49
const StyledTableHead = styled(TableHead)({
1✔
50
  background: "#083A50",
1✔
51
});
1✔
52

53
const StyledHeaderCell = styled(TableCell)({
1✔
54
  fontWeight: 700,
1✔
55
  fontSize: "14px",
1✔
56
  color: "#fff !important",
1✔
57
  "&.MuiTableCell-root": {
1✔
58
    padding: "8px 16px",
1✔
59
    color: "#fff !important",
1✔
60
  },
1✔
61
  "& .MuiSvgIcon-root,  & .MuiButtonBase-root": {
1✔
62
    color: "#fff !important",
1✔
63
  },
1✔
64
});
1✔
65

66
const StyledTableCell = styled(TableCell)({
1✔
67
  fontSize: "14px",
1✔
68
  color: "#083A50 !important",
1✔
69
  textAlign: "left",
1✔
70
  "&.MuiTableCell-root": {
1✔
71
    padding: "8px 16px",
1✔
72
  },
1✔
73
});
1✔
74

75
const StyledLink = styled(Link)({
1✔
76
  textDecoration: "none",
1✔
77
  display: "flex",
1✔
78
  justifyContent: "center",
1✔
79
  alignItems: "center",
1✔
80
  width: "100px",
1✔
81
});
1✔
82

83
const StyledActionButton = styled(Button)(
1✔
84
  ({ bg, text, border }: { bg: string; text: string; border: string }) => ({
1✔
85
    display: "flex",
5✔
86
    justifyContent: "center",
5✔
87
    alignItems: "center",
5✔
88
    background: `${bg} !important`,
5✔
89
    borderRadius: "8px",
5✔
90
    border: `2px solid ${border}`,
5✔
91
    color: `${text} !important`,
5✔
92
    width: "100px",
5✔
93
    height: "30px",
5✔
94
    textTransform: "none",
5✔
95
    fontWeight: 700,
5✔
96
    fontSize: "16px",
5✔
97
  })
5✔
98
);
1✔
99

100
const StyledDateTooltip = styled(StyledTooltip)(() => ({
1✔
UNCOV
101
  cursor: "pointer",
×
102
}));
1✔
103

104
const StyledAsterisk = styled(Asterisk)(() => ({
1✔
105
  cursor: "pointer",
12✔
106
}));
1✔
107

108
const columns: Column<ApprovedStudy>[] = [
1✔
109
  {
1✔
110
    label: "Name",
1✔
111
    renderValue: (a) => <TruncatedText text={a.studyName} />,
1✔
112
    field: "studyName",
1✔
113
    default: true,
1✔
114
    sx: {
1✔
115
      width: "144px",
1✔
116
    },
1✔
117
  },
1✔
118
  {
1✔
119
    label: "Acronym",
1✔
120
    renderValue: (a) => {
1✔
121
      const pendingConditions = [
9✔
122
        {
9✔
123
          check: a.controlledAccess && !a.dbGaPID?.trim()?.length,
9✔
124
          tooltip: "Data submission is Pending on dbGaPID Registration.",
9✔
125
        },
9✔
126
        {
9✔
127
          check: a.pendingModelChange,
9✔
128
          tooltip: "Data submission is Pending on Data Model Review.",
9✔
129
        },
9✔
130
      ]
131
        .filter((pc) => pc.check)
9✔
132
        .map((pc) => pc.tooltip);
9✔
133

134
      return (
9✔
135
        <>
9✔
136
          <TruncatedText text={a.studyAbbreviation} />
9✔
137
          {pendingConditions?.length > 0 ? (
9✔
138
            <StyledTooltip title={<TooltipList data={pendingConditions} />} placement="top" arrow>
6✔
139
              <StyledAsterisk data-testid={`asterisk-${a.studyAbbreviation}`} />
6✔
140
            </StyledTooltip>
6✔
141
          ) : null}
3✔
142
        </>
9✔
143
      );
144
    },
9✔
145
    field: "studyAbbreviation",
1✔
146
    sx: {
1✔
147
      width: "144px",
1✔
148
    },
1✔
149
  },
1✔
150
  {
1✔
151
    label: "dbGaPID",
1✔
152
    renderValue: (a) => <TruncatedText text={a.dbGaPID} maxCharacters={10} />,
1✔
153
    field: "dbGaPID",
1✔
154
    sx: {
1✔
155
      width: "144px",
1✔
156
    },
1✔
157
  },
1✔
158
  {
1✔
159
    label: "Access Type",
1✔
160
    renderValue: (a) => (
1✔
161
      <TruncatedText text={formatAccessTypes(a.controlledAccess, a.openAccess)} />
9✔
162
    ),
163
    fieldKey: "accessType",
1✔
164
    sortDisabled: true,
1✔
165
    sx: {
1✔
166
      width: "140px",
1✔
167
    },
1✔
168
  },
1✔
169
  {
1✔
170
    label: "Principal Investigator",
1✔
171
    renderValue: (a) => <TruncatedText text={a.PI} />,
1✔
172
    field: "PI",
1✔
173
    sx: {
1✔
174
      width: "144px",
1✔
175
    },
1✔
176
  },
1✔
177
  {
1✔
178
    label: "ORCID",
1✔
179
    renderValue: (a) => <TruncatedText text={a.ORCID} />,
1✔
180
    field: "ORCID",
1✔
181
    sx: {
1✔
182
      width: "144px",
1✔
183
    },
1✔
184
  },
1✔
185
  {
1✔
186
    label: "Program",
1✔
187
    renderValue: ({ programs }) => (
1✔
188
      <SummaryList
9✔
189
        data={programs}
9✔
190
        getItemKey={(p) => p._id}
9✔
191
        renderItem={(p) => <TruncatedText text={p.name} maxCharacters={8} />}
9✔
192
        renderTooltipItem={(p) => p.name}
9✔
193
        emptyText=""
9✔
194
      />
195
    ),
196
    fieldKey: "programs.name",
1✔
197
    sx: {
1✔
198
      width: "380px",
1✔
199
    },
1✔
200
  },
1✔
201
  {
1✔
202
    label: "Data Concierge",
1✔
203
    renderValue: (a) => {
1✔
204
      const primaryContactName = a.primaryContact
9✔
205
        ? `${a.primaryContact?.firstName || ""} ${a.primaryContact?.lastName || ""}`?.trim()
9!
206
        : "";
×
207
      return <TruncatedText text={primaryContactName || ""} />;
9✔
208
    },
9✔
209
    fieldKey: "primaryContact.firstName",
1✔
210
  },
1✔
211
  {
1✔
212
    label: "Created Date",
1✔
213
    renderValue: (a) =>
1✔
214
      a.createdAt ? (
9!
UNCOV
215
        <StyledDateTooltip title={FormatDate(a.createdAt, "M/D/YYYY h:mm A")} placement="top">
×
UNCOV
216
          <span>{FormatDate(a.createdAt, "M/D/YYYY")}</span>
×
217
        </StyledDateTooltip>
×
218
      ) : (
219
        ""
9✔
220
      ),
221
    field: "createdAt",
1✔
222
  },
1✔
223
  {
1✔
224
    label: (
1✔
225
      <Stack direction="row" justifyContent="center" alignItems="center">
1✔
226
        Action
227
      </Stack>
1✔
228
    ),
229
    renderValue: (a) => (
1✔
230
      <StyledLink to={`/studies/${a?._id}`}>
9✔
231
        <StyledActionButton bg="#C5EAF2" text="#156071" border="#84B4BE">
9✔
232
          Edit
233
        </StyledActionButton>
9✔
234
      </StyledLink>
9✔
235
    ),
236
    sortDisabled: true,
1✔
237
    sx: {
1✔
238
      width: "100px",
1✔
239
    },
1✔
240
  },
1✔
241
];
242

243
const ListView = () => {
1✔
244
  usePageTitle("Manage Studies");
34✔
245

246
  const { state } = useLocation();
34✔
247

248
  const [loading, setLoading] = useState<boolean>(false);
34✔
249
  const [error, setError] = useState<boolean>(false);
34✔
250
  const [data, setData] = useState<ApprovedStudy[]>([]);
34✔
251
  const [count, setCount] = useState<number>(0);
34✔
252
  const filtersRef = useRef<FilterForm>({
34✔
253
    study: "",
34✔
254
    programID: "All",
34✔
255
    dbGaPID: "",
34✔
256
    accessType: "All",
34✔
257
  });
34✔
258

259
  const tableRef = useRef<TableMethods>(null);
34✔
260

261
  const [listSubmissions] = useLazyQuery<ListApprovedStudiesResp, ListApprovedStudiesInput>(
34✔
262
    LIST_APPROVED_STUDIES,
34✔
263
    {
34✔
264
      context: { clientName: "backend" },
34✔
265
      fetchPolicy: "cache-and-network",
34✔
266
    }
34✔
267
  );
34✔
268

269
  const handleFetchData = async (fetchListing: FetchListing<ApprovedStudy>, force: boolean) => {
34✔
270
    const { first, offset, sortDirection, orderBy } = fetchListing || {};
8!
271

272
    if (!filtersRef.current) {
8!
UNCOV
273
      return;
×
UNCOV
274
    }
×
275

276
    try {
8✔
277
      setLoading(true);
8✔
278

279
      const { data: d, error } = await listSubmissions({
8✔
280
        variables: {
8✔
281
          first,
8✔
282
          offset,
8✔
283
          sortDirection,
8✔
284
          orderBy,
8✔
285
          dbGaPID: filtersRef.current.dbGaPID,
8✔
286
          controlledAccess: filtersRef.current.accessType,
8✔
287
          study: filtersRef.current.study,
8✔
288
          programID: filtersRef.current.programID,
8✔
289
        },
8✔
290
        context: { clientName: "backend" },
8✔
291
        fetchPolicy: "no-cache",
8✔
292
      });
8✔
293
      if (error || !d?.listApprovedStudies) {
8!
UNCOV
294
        throw new Error("Unable to retrieve List Approved Studies results.");
×
UNCOV
295
      }
×
296

297
      setData(d.listApprovedStudies.studies);
8✔
298
      setCount(d.listApprovedStudies.total);
8✔
299
    } catch (err) {
8!
UNCOV
300
      setError(true);
×
301
    } finally {
8✔
302
      setLoading(false);
8✔
303
    }
8✔
304
  };
8✔
305

306
  const setTablePage = (page: number) => {
34✔
307
    tableRef.current?.setPage(page, true);
8✔
308
  };
8✔
309

310
  const handleOnFiltersChange = (data: FilterForm) => {
34✔
311
    filtersRef.current = data;
8✔
312
    setTablePage(0);
8✔
313
  };
8✔
314

315
  return (
34✔
316
    <Box data-testid="list-studies-container">
34✔
317
      <Container maxWidth="xl">
34✔
318
        {(state?.error || error) && (
34!
UNCOV
319
          <Alert sx={{ mt: 2, mx: "auto", p: 2 }} severity="error">
×
UNCOV
320
            {state?.error || "An error occurred while loading the data."}
×
UNCOV
321
          </Alert>
×
322
        )}
323
      </Container>
34✔
324

325
      <PageBanner
34✔
326
        title="Manage Studies"
34✔
327
        subTitle=""
34✔
328
        padding="38px 25px 0"
34✔
329
        body={
34✔
330
          <StyledBannerBody direction="row" alignItems="center" justifyContent="flex-end">
34✔
331
            <StyledButton component={Link} to="/studies/new">
34✔
332
              Add Study
333
            </StyledButton>
34✔
334
          </StyledBannerBody>
34✔
335
        }
34✔
336
      />
337

338
      <StyledContainer maxWidth="xl">
34✔
339
        <ApprovedStudyFilters onChange={handleOnFiltersChange} />
34✔
340

341
        <GenericTable
34✔
342
          ref={tableRef}
34✔
343
          columns={columns}
34✔
344
          data={data || []}
34!
345
          total={count || 0}
34✔
346
          loading={loading}
34✔
347
          disableUrlParams={false}
34✔
348
          defaultRowsPerPage={20}
34✔
349
          defaultOrder="asc"
34✔
350
          setItemKey={(item, idx) => `${idx}_${item._id}`}
34✔
351
          onFetchData={handleFetchData}
34✔
352
          containerProps={{ sx: { marginBottom: "8px", borderColor: "#083A50" } }}
34✔
353
          CustomTableHead={StyledTableHead}
34✔
354
          CustomTableHeaderCell={StyledHeaderCell}
34✔
355
          CustomTableBodyCell={StyledTableCell}
34✔
356
        />
357
      </StyledContainer>
34✔
358
    </Box>
34✔
359
  );
360
};
34✔
361

362
export default ListView;
1✔
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