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

CBIIT / crdc-datahub-ui / 15742301496

18 Jun 2025 07:53PM UTC coverage: 70.808% (-0.2%) from 71.05%
15742301496

push

github

web-flow
Merge pull request #743 from CBIIT/CRDCDH-2789

CRDCDH-2789 Data Explorer - Study List View

3533 of 3890 branches covered (90.82%)

Branch coverage included in aggregate %.

425 of 701 new or added lines in 11 files covered. (60.63%)

19 existing lines in 3 files now uncovered.

22472 of 32836 relevant lines covered (68.44%)

110.84 hits per line

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

65.9
/src/components/Header/components/HeaderTabletAndMobile.tsx
1
import React, { HTMLProps, useEffect, useMemo, useState } from "react";
1✔
2
import { NavLink, Link, useNavigate, useLocation } from "react-router-dom";
1✔
3
import { flatMap } from "lodash";
1✔
4
import { styled } from "@mui/material";
1✔
5
import { useSnackbar } from "notistack";
1✔
6
import Logo from "./LogoMobile";
1✔
7
import menuClearIcon from "../../../assets/header/Menu_Cancel_Icon.svg?url";
1✔
8
import rightArrowIcon from "../../../assets/header/Right_Arrow.svg?url";
1✔
9
import leftArrowIcon from "../../../assets/header/Left_Arrow.svg?url";
1✔
10
import { ActionHandlers, ActionId, HeaderLinks } from "../../../config/HeaderConfig";
1✔
11
import { useAuthContext } from "../../Contexts/AuthContext";
1✔
12
import APITokenDialog from "../../APITokenDialog";
1✔
13
import UploaderToolDialog from "../../UploaderToolDialog";
1✔
14
import { hasPermission, Permissions } from "../../../config/AuthPermissions";
1✔
15
import { Logger } from "../../../utils";
1✔
16

17
const HeaderBanner = styled("div")({
1✔
18
  width: "100%",
1✔
19
});
1✔
20

21
const HeaderContainer = styled("div")({
1✔
22
  margin: "0 auto",
1✔
23
  paddingLeft: "16px",
1✔
24
  boxShadow: "-0.1px 6px 9px -6px rgba(0, 0, 0, 0.5)",
1✔
25
  "& .headerLowerContainer": {
1✔
26
    display: "flex",
1✔
27
    margin: "16px 0 4px 0",
1✔
28
    height: "51px",
1✔
29
  },
1✔
30
  "& .menuButton": {
1✔
31
    width: "89px",
1✔
32
    height: "45px",
1✔
33
    background: "#1F4671",
1✔
34
    borderRadius: "5px",
1✔
35
    fontFamily: "Open Sans",
1✔
36
    fontWeight: 700,
1✔
37
    fontSize: "20px",
1✔
38
    lineHeight: "45px",
1✔
39
    color: "#FFFFFF",
1✔
40
    textAlign: "center",
1✔
41
    "&:hover": {
1✔
42
      cursor: "pointer",
1✔
43
    },
1✔
44
  },
1✔
45
});
1✔
46

47
const NavMobileContainer = styled("div", {
1✔
48
  shouldForwardProp: (prop) => prop !== "display",
1✔
49
})<HTMLProps<HTMLDivElement> & { display: string }>(({ display }) => ({
1✔
50
  display,
4✔
51
  position: "absolute",
4✔
52
  left: 0,
4✔
53
  top: 0,
4✔
54
  height: "100%",
4✔
55
  width: "100%",
4✔
56
  zIndex: 1200,
4✔
57
}));
1✔
58

59
const MenuArea = styled("div")({
1✔
60
  height: "100%",
1✔
61
  width: "100%",
1✔
62
  display: "flex",
1✔
63
  "& .menuContainer": {
1✔
64
    background: "#ffffff",
1✔
65
    width: "300px",
1✔
66
    height: "100%",
1✔
67
    padding: "21px 16px",
1✔
68
  },
1✔
69
  "& .greyContainer": {
1✔
70
    width: "100%",
1✔
71
    height: "100%",
1✔
72
    background: "rgba(0,0,0,.2)",
1✔
73
  },
1✔
74
  "& .closeIcon": {
1✔
75
    height: "14px",
1✔
76
    marginBottom: "29px",
1✔
77
  },
1✔
78
  "& .closeIconImg": {
1✔
79
    float: "right",
1✔
80
    "&:hover": {
1✔
81
      cursor: "pointer",
1✔
82
    },
1✔
83
  },
1✔
84
  "& .backButton": {
1✔
85
    fontFamily: "Open Sans",
1✔
86
    fontWeight: 600,
1✔
87
    fontSize: "16px",
1✔
88
    lineHeight: "16px",
1✔
89
    color: "#007BBD",
1✔
90
    paddingLeft: "16px",
1✔
91
    background: `url("${leftArrowIcon}") left no-repeat`,
1✔
92
    "&:hover": {
1✔
93
      cursor: "pointer",
1✔
94
    },
1✔
95
  },
1✔
96
  "& .navMobileContainer": {
1✔
97
    padding: "24px 0 0 0",
1✔
98
    "& a": {
1✔
99
      textDecoration: "none",
1✔
100
      color: "#3D4551",
1✔
101
    },
1✔
102
  },
1✔
103
  "& .navMobileItem": {
1✔
104
    width: "268px",
1✔
105
    padding: "8px 24px 8px 16px",
1✔
106
    fontFamily: "Open Sans",
1✔
107
    fontWeight: 400,
1✔
108
    fontSize: "16px",
1✔
109
    lineHeight: "16px",
1✔
110
    borderTop: "1px solid #F0F0F0",
1✔
111
    borderBottom: "1px solid #F0F0F0",
1✔
112
    color: "#3D4551",
1✔
113
    "&:hover": {
1✔
114
      backgroundColor: "#f9f9f7",
1✔
115
    },
1✔
116
  },
1✔
117
  "& .SubItem": {
1✔
118
    paddingLeft: "24px",
1✔
119
  },
1✔
120
  "& .clickable": {
1✔
121
    background: `url("${rightArrowIcon}") 90% no-repeat`,
1✔
122
    cursor: "pointer",
1✔
123
  },
1✔
124
  "& .action": {
1✔
125
    cursor: "pointer",
1✔
126
  },
1✔
127
});
1✔
128

129
const Header = () => {
1✔
130
  const { isLoggedIn, user, logout } = useAuthContext();
4✔
131
  const navigate = useNavigate();
4✔
132
  const location = useLocation();
4✔
133
  const { enqueueSnackbar } = useSnackbar();
4✔
134

135
  const [navMobileDisplay, setNavMobileDisplay] = useState("none");
4✔
136
  const [openAPITokenDialog, setOpenAPITokenDialog] = useState<boolean>(false);
4✔
137
  const [uploaderToolOpen, setUploaderToolOpen] = useState<boolean>(false);
4✔
138
  const [selectedList, setSelectedList] = useState<NavBarItem[] | NavBarSubItem[]>(HeaderLinks);
4✔
139
  const [restorePath, setRestorePath] = useState<string | null>(null);
4✔
140

141
  const displayName = user?.firstName || "N/A";
4!
142

143
  const handleLogout = async () => {
4✔
144
    const logoutStatus = await logout?.();
×
145
    if (logoutStatus) {
×
146
      navigate("/");
×
147
      enqueueSnackbar("You have been logged out.", { variant: "success" });
×
148
    }
×
149
  };
×
150

151
  const actionHandlers: ActionHandlers = useMemo(
4✔
152
    () => ({
4✔
153
      logout: handleLogout,
4✔
154
      openAPITokenDialog: () => setOpenAPITokenDialog(true),
4✔
155
      openCLIToolDialog: () => setUploaderToolOpen(true),
4✔
156
    }),
4✔
157
    [logout]
4✔
158
  );
4✔
159

160
  const handleItemClick = (item: ActionId) => {
4✔
161
    if (!item) {
×
162
      Logger.error(`HeaderTabletAndMobile.tsx: No action found for actionId '${item}'`);
×
163
      return;
×
164
    }
×
165

166
    actionHandlers[item]?.();
×
167
  };
×
168

169
  const clickNavItem = (clickTitle: string) => {
4✔
170
    const list: NavBarItem = HeaderLinks?.find(
×
171
      (link) => link.name === clickTitle && "columns" in link
×
172
    );
×
173
    setSelectedList(flatMap(list?.columns));
×
174
  };
×
175

176
  const checkPermissions = (permissions: AuthPermissions[]) => {
4✔
177
    if (!permissions?.length) {
24!
178
      return true; // No permissions required
24✔
179
    }
24!
180

UNCOV
181
    return permissions.every((permission) => {
×
UNCOV
182
      const [entityRaw, actionRaw] = permission.split(":", 2);
×
183

UNCOV
184
      if (!entityRaw || !actionRaw) {
×
185
        return false;
×
186
      }
×
187

UNCOV
188
      const entity = entityRaw as keyof Permissions;
×
UNCOV
189
      const action = actionRaw as Permissions[keyof Permissions]["action"];
×
190

UNCOV
191
      return hasPermission(user, entity, action, null, true);
×
UNCOV
192
    });
×
193
  };
24✔
194

195
  useEffect(() => {
4✔
196
    if (!location?.pathname || location?.pathname === "/") {
4✔
197
      setRestorePath(null);
4✔
198
      return;
4✔
199
    }
4!
200

201
    setRestorePath(location?.pathname);
4✔
202
  }, [location]);
4✔
203

204
  return (
4✔
205
    <>
4✔
206
      <HeaderBanner data-testid="navigation-header-mobile">
4✔
207
        <HeaderContainer>
4✔
208
          <Logo />
4✔
209
          <div className="headerLowerContainer">
4✔
210
            <div
4✔
211
              role="button"
4✔
212
              id="header-navbar-open-menu-button"
4✔
213
              tabIndex={0}
4✔
214
              className="menuButton"
4✔
215
              onKeyDown={(e) => {
4✔
216
                if (e.key === "Enter") {
×
217
                  setNavMobileDisplay("block");
×
218
                }
×
219
              }}
×
220
              onClick={() => setNavMobileDisplay("block")}
4✔
221
            >
222
              Menu
223
            </div>
4✔
224
          </div>
4✔
225
        </HeaderContainer>
4✔
226
      </HeaderBanner>
4✔
227
      <NavMobileContainer display={navMobileDisplay}>
4✔
228
        <MenuArea>
4✔
229
          <div className="menuContainer">
4✔
230
            <div
4✔
231
              role="button"
4✔
232
              id="navbar-close-navbar-button"
4✔
233
              tabIndex={0}
4✔
234
              className="closeIcon"
4✔
235
              onKeyDown={(e) => {
4✔
236
                if (e.key === "Enter") {
×
237
                  setNavMobileDisplay("none");
×
238
                }
×
239
              }}
×
240
              onClick={() => setNavMobileDisplay("none")}
4✔
241
            >
242
              <img className="closeIconImg" src={menuClearIcon} alt="menuClearButton" />
4✔
243
            </div>
4✔
244
            {selectedList !== HeaderLinks && (
4!
245
              <div
×
246
                role="button"
×
247
                id="navbar-back-to-main-menu-button"
×
248
                tabIndex={0}
×
249
                className="backButton"
×
250
                onKeyDown={(e) => {
×
251
                  if (e.key === "Enter") {
×
252
                    setSelectedList(HeaderLinks);
×
253
                  }
×
254
                }}
×
255
                onClick={() => setSelectedList(HeaderLinks)}
×
256
              >
257
                Main Menu
258
              </div>
×
259
            )}
260
            <div className="navMobileContainer">
4✔
261
              {selectedList
4✔
262
                ?.filter((headerLinks) => headerLinks.name !== "User")
4✔
263
                ?.map((navMobileItem: NavBarItem | NavBarSubItem) => {
4✔
264
                  const hasEveryPermission = checkPermissions(navMobileItem?.permissions);
24✔
265
                  if (!hasEveryPermission) {
24!
UNCOV
266
                    return null;
×
UNCOV
267
                  }
×
268

269
                  return (
24✔
270
                    <React.Fragment key={`mobile_${navMobileItem.id}`}>
24✔
271
                      {navMobileItem.className === "navMobileItem" && (
24✔
272
                        <NavLink
16✔
273
                          id={navMobileItem.id}
16✔
274
                          to={navMobileItem.link}
16✔
275
                          target={navMobileItem.link.startsWith("https://") ? "_blank" : "_self"}
16✔
276
                          onClick={() => setNavMobileDisplay("none")}
16✔
277
                        >
278
                          <div className="navMobileItem">{navMobileItem.name}</div>
16✔
279
                        </NavLink>
16✔
280
                      )}
281
                      {navMobileItem.className === "navMobileItem clickable" && (
24✔
282
                        <div
8✔
283
                          id={navMobileItem.id}
8✔
284
                          role="button"
8✔
285
                          tabIndex={0}
8✔
286
                          className="navMobileItem clickable"
8✔
287
                          onKeyDown={(e) => {
8✔
288
                            if (e.key === "Enter") {
×
289
                              clickNavItem(navMobileItem.name);
×
290
                            }
×
291
                          }}
×
292
                          onClick={() => clickNavItem(navMobileItem.name)}
8✔
293
                        >
294
                          {navMobileItem.name}
8✔
295
                        </div>
8✔
296
                      )}
297
                      {navMobileItem.className === "navMobileSubItem action" &&
24!
298
                      "actionId" in navMobileItem &&
×
299
                      typeof navMobileItem.actionId === "string" ? (
×
300
                        <div
×
301
                          id={navMobileItem.id}
×
302
                          role="button"
×
303
                          tabIndex={0}
×
304
                          className="navMobileItem SubItem action"
×
305
                          onKeyDown={(e) => {
×
306
                            if (e.key === "Enter") {
×
307
                              handleItemClick(navMobileItem.actionId as ActionId);
×
308
                            }
×
309
                          }}
×
310
                          onClick={() => handleItemClick(navMobileItem.actionId as ActionId)}
×
311
                        >
312
                          {navMobileItem.name}
×
313
                        </div>
×
314
                      ) : null}
24✔
315
                      {navMobileItem.className === "navMobileSubItem" && (
24!
316
                        <Link
×
317
                          id={navMobileItem.id}
×
318
                          to={navMobileItem.link}
×
319
                          target={
×
320
                            navMobileItem.link.startsWith("https://") ||
×
321
                            navMobileItem.link.endsWith(".pdf")
×
322
                              ? "_blank"
×
323
                              : "_self"
×
324
                          }
325
                        >
326
                          <div
×
327
                            role="button"
×
328
                            tabIndex={0}
×
329
                            className="navMobileItem SubItem"
×
330
                            onKeyDown={(e) => {
×
331
                              if (e.key === "Enter") {
×
332
                                setNavMobileDisplay("none");
×
333
                                if (navMobileItem.name === "Logout") {
×
334
                                  handleLogout();
×
335
                                  setSelectedList(HeaderLinks);
×
336
                                }
×
337
                              }
×
338
                            }}
×
339
                            onClick={() => {
×
340
                              setNavMobileDisplay("none");
×
341
                              if (navMobileItem.name === "Logout") {
×
342
                                handleLogout();
×
343
                                setSelectedList(HeaderLinks);
×
344
                              }
×
345
                            }}
×
346
                          >
347
                            {navMobileItem.name}
×
348
                          </div>
×
349
                        </Link>
×
350
                      )}
351
                      {navMobileItem.className === "navMobileSubTitle" && (
24!
352
                        <div className="navMobileItem">{navMobileItem.name}</div>
×
353
                      )}
354
                    </React.Fragment>
24✔
355
                  );
356
                })}
4✔
357
              {/* eslint-disable-next-line no-nested-ternary */}
358
              {selectedList === HeaderLinks ? (
4✔
359
                isLoggedIn ? (
4!
360
                  <div
×
361
                    id="navbar-dropdown-name"
×
362
                    role="button"
×
363
                    tabIndex={0}
×
364
                    className="navMobileItem clickable"
×
365
                    onKeyDown={(e) => {
×
366
                      if (e.key === "Enter") {
×
367
                        clickNavItem("User");
×
368
                      }
×
369
                    }}
×
370
                    onClick={() => clickNavItem("User")}
×
371
                  >
372
                    {displayName}
×
373
                  </div>
×
374
                ) : (
375
                  <Link id="navbar-link-login" to="/login" state={{ redirectState: restorePath }}>
4✔
376
                    <div
4✔
377
                      role="button"
4✔
378
                      tabIndex={0}
4✔
379
                      className="navMobileItem"
4✔
380
                      onKeyDown={(e) => {
4✔
381
                        if (e.key === "Enter") {
×
382
                          setNavMobileDisplay("none");
×
383
                        }
×
384
                      }}
×
385
                      onClick={() => setNavMobileDisplay("none")}
4✔
386
                    >
387
                      Login
388
                    </div>
4✔
389
                  </Link>
4!
390
                )
391
              ) : null}
×
392
            </div>
4✔
393
          </div>
4✔
394
          <div
4✔
395
            role="button"
4✔
396
            id="navbar-close-navbar-grey-section"
4✔
397
            tabIndex={0}
4✔
398
            className="greyContainer"
4✔
399
            onKeyDown={(e) => {
4✔
400
              if (e.key === "Enter") {
×
401
                setNavMobileDisplay("none");
×
402
              }
×
403
            }}
×
404
            onClick={() => setNavMobileDisplay("none")}
4✔
405
            aria-label="greyContainer"
4✔
406
          />
407
        </MenuArea>
4✔
408
        <APITokenDialog open={openAPITokenDialog} onClose={() => setOpenAPITokenDialog(false)} />
4✔
409
        <UploaderToolDialog open={uploaderToolOpen} onClose={() => setUploaderToolOpen(false)} />
4✔
410
      </NavMobileContainer>
4✔
411
    </>
4✔
412
  );
413
};
4✔
414

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

© 2026 Coveralls, Inc