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

CBIIT / crdc-datahub-ui / 16427907648

21 Jul 2025 08:53PM UTC coverage: 73.695% (+0.06%) from 73.639%
16427907648

Pull #797

github

web-flow
Merge 2a021534c into d93ea77f5
Pull Request #797: CRDCDH-2990 Manage Collaborators with 'No Access'

3843 of 4255 branches covered (90.32%)

Branch coverage included in aggregate %.

90 of 94 new or added lines in 2 files covered. (95.74%)

4 existing lines in 1 file now uncovered.

24607 of 34350 relevant lines covered (71.64%)

115.6 hits per line

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

98.8
/src/components/Collaborators/CollaboratorsTable.tsx
1
import AddCircleIcon from "@mui/icons-material/AddCircle";
1✔
2
import {
1✔
3
  FormControlLabel,
4
  IconButton,
5
  MenuItem,
6
  RadioGroup,
7
  Stack,
8
  styled,
9
  Table,
10
  TableBody,
11
  TableCell,
12
  TableContainer,
13
  TableHead,
14
  TableRow,
15
} from "@mui/material";
16
import { isEqual } from "lodash";
1✔
17
import React from "react";
1✔
18

19
import RemoveIconSvg from "../../assets/icons/remove_icon.svg?react";
1✔
20
import { TOOLTIP_TEXT } from "../../config/DashboardTooltips";
1✔
21
import AddRemoveButton from "../AddRemoveButton";
1✔
22
import { useCollaboratorsContext } from "../Contexts/CollaboratorsContext";
1✔
23
import StyledFormRadioButton from "../Questionnaire/StyledRadioButton";
1✔
24
import StyledFormSelect from "../StyledFormComponents/StyledSelect";
1✔
25
import TruncatedText from "../TruncatedText";
1✔
26

27
const StyledTableContainer = styled(TableContainer)(() => ({
1✔
28
  borderRadius: "8px !important",
44✔
29
  border: "1px solid #6B7294",
44✔
30
  overflow: "hidden",
44✔
31
  marginBottom: "15px",
44✔
32
}));
1✔
33

34
const FixedTable = styled(Table)({
1✔
35
  tableLayout: "fixed",
1✔
36
  width: "100%",
1✔
37
});
1✔
38

39
const StyledTableHeaderRow = styled(TableRow)(() => ({
1✔
40
  "&.MuiTableRow-root": {
44✔
41
    height: "38px",
44✔
42
    padding: 0,
44✔
43
    justifyContent: "space-between",
44✔
44
    alignItems: "center",
44✔
45
    background: "#FFF",
44✔
46
    borderBottom: "1px solid #6B7294",
44✔
47
  },
44✔
48
}));
1✔
49

50
const StyledTableHeaderCell = styled(TableCell)(() => ({
1✔
51
  "&.MuiTableCell-root": {
123✔
52
    height: "100%",
123✔
53
    color: "#083A50",
123✔
54
    fontSize: "16px",
123✔
55
    fontStyle: "normal",
123✔
56
    fontWeight: 700,
123✔
57
    lineHeight: "14px",
123✔
58
    padding: "5px 12px",
123✔
59
    borderBottom: "0 !important",
123✔
60
    borderRight: "1px solid #6B7294",
123✔
61
    "&:last-child": {
123✔
62
      borderRight: "none",
123✔
63
    },
123✔
64
  },
123✔
65
}));
1✔
66

67
const StyledTableRow = styled(TableRow)(() => ({
1✔
68
  "&.MuiTableRow-root": {
27✔
69
    height: "38px",
27✔
70
    padding: 0,
27✔
71
    justifyContent: "space-between",
27✔
72
    alignItems: "center",
27✔
73
    background: "#FFF",
27✔
74
    borderBottom: "1px solid #6B7294",
27✔
75
    "&:last-child": {
27✔
76
      borderBottom: "none",
27✔
77
      "& .MuiOutlinedInput-notchedOutline, & .MuiSelect-select": {
27✔
78
        borderBottomLeftRadius: "8px !important",
27✔
79
      },
27✔
80
    },
27✔
81
  },
27✔
82
}));
1✔
83

84
const StyledTableCell = styled(TableCell)(() => ({
1✔
85
  "&.MuiTableCell-root": {
77✔
86
    height: "100%",
77✔
87
    color: "#083A50",
77✔
88
    fontSize: "16px",
77✔
89
    fontStyle: "normal",
77✔
90
    fontWeight: 400,
77✔
91
    lineHeight: "16px",
77✔
92
    padding: "4px 12px",
77✔
93
    borderBottom: "0 !important",
77✔
94
    borderRight: "1px solid #6B7294",
77✔
95
    "&:last-child": {
77✔
96
      borderRight: "none",
77✔
97
    },
77✔
98
  },
77✔
99
}));
1✔
100

101
const StyledNameCell = styled(StyledTableCell)({
1✔
102
  "&.MuiTableCell-root": {
1✔
103
    padding: 0,
1✔
104
  },
1✔
105
});
1✔
106

107
const StyledRadioControl = styled(FormControlLabel)({
1✔
108
  fontFamily: "Nunito",
1✔
109
  fontSize: "16px",
1✔
110
  fontWeight: "500",
1✔
111
  lineHeight: "20px",
1✔
112
  textAlign: "left",
1✔
113
  color: "#083A50",
1✔
114
  "&:last-child": {
1✔
115
    marginRight: "0px",
1✔
116
    minWidth: "unset",
1✔
117
  },
1✔
118
});
1✔
119

120
const StyledRadioGroup = styled(RadioGroup)({
1✔
121
  width: "100%",
1✔
122
  justifyContent: "center",
1✔
123
  alignItems: "center",
1✔
124
  gap: "14px",
1✔
125
  "& .MuiFormControlLabel-root": {
1✔
126
    margin: 0,
1✔
127
    "&.Mui-disabled": {
1✔
128
      cursor: "not-allowed",
1✔
129
    },
1✔
130
  },
1✔
131
  "& .MuiFormControlLabel-asterisk": {
1✔
132
    display: "none",
1✔
133
  },
1✔
134
  "& .MuiSelect-select .notranslate": {
1✔
135
    display: "inline-block",
1✔
136
    minHeight: "38px",
1✔
137
  },
1✔
138
  "& .MuiRadio-root.Mui-disabled .radio-icon": {
1✔
139
    background: "#FFF !important",
1✔
140
    opacity: 0.4,
1✔
141
  },
1✔
142
});
1✔
143

144
const StyledRadioButton = styled(StyledFormRadioButton)({
1✔
145
  padding: "0 7px 0 0",
1✔
146
});
1✔
147

148
const StyledRemoveButton = styled(IconButton)(({ theme }) => ({
1✔
149
  color: "#C05239",
23✔
150
  padding: "5px",
23✔
151
  "&.Mui-disabled": {
23✔
152
    opacity: theme.palette.action.disabledOpacity,
23✔
153
  },
23✔
154
}));
1✔
155

156
const StyledSelect = styled(StyledFormSelect)({
1✔
157
  "&.MuiInputBase-root": {
1✔
158
    paddingTop: 0,
1✔
159
    paddingBottom: 0,
1✔
160
    display: "block",
1✔
161
  },
1✔
162
  "& .MuiInputBase-input": {
1✔
163
    minHeight: "38px !important",
1✔
164
    lineHeight: "20px",
1✔
165
    paddingTop: "9px",
1✔
166
    paddingBottom: "9px",
1✔
167
    height: "100%",
1✔
168
    borderRadius: 0,
1✔
169
    boxSizing: "border-box",
1✔
170
  },
1✔
171
  "& .MuiOutlinedInput-notchedOutline": {
1✔
172
    border: "0 !important",
1✔
173
    boxShadow: "none",
1✔
174
    borderRadius: 0,
1✔
175
  },
1✔
176
  "& .MuiSelect-nativeInput": {
1✔
177
    padding: 0,
1✔
178
  },
1✔
179
  "& .Mui-readOnly.MuiOutlinedInput-input:read-only": {
1✔
180
    borderRadius: 0,
1✔
181
  },
1✔
182
});
1✔
183

184
type Props = {
185
  /**
186
   * Indicates whether the table will allow edititing of collaborators
187
   */
188
  isEdit: boolean;
189
};
190

191
const CollaboratorsTable = ({ isEdit }: Props) => {
1✔
192
  const {
44✔
193
    currentCollaborators,
44✔
194
    remainingPotentialCollaborators,
44✔
195
    maxCollaborators,
44✔
196
    handleAddCollaborator,
44✔
197
    handleRemoveCollaborator,
44✔
198
    handleUpdateCollaborator,
44✔
199
    loading,
44✔
200
  } = useCollaboratorsContext();
44✔
201

202
  return (
44✔
203
    <>
44✔
204
      <StyledTableContainer data-testid="collaborators-table-container">
44✔
205
        <FixedTable>
44✔
206
          <colgroup>
44✔
207
            <col style={{ width: isEdit ? "47%" : "50%" }} />
44✔
208
            <col style={{ width: isEdit ? "40%" : "50%" }} />
44✔
209
            {isEdit && <col style={{ width: "13%" }} />}
44✔
210
          </colgroup>
44✔
211

212
          <TableHead>
44✔
213
            <StyledTableHeaderRow data-testid="table-header-row">
44✔
214
              <StyledTableHeaderCell id="header-collaborator" data-testid="header-collaborator">
44✔
215
                Collaborator
216
              </StyledTableHeaderCell>
44✔
217
              <StyledTableHeaderCell
44✔
218
                id="header-access"
44✔
219
                sx={{ textAlign: "center" }}
44✔
220
                data-testid="header-access"
44✔
221
              >
222
                Access
223
              </StyledTableHeaderCell>
44✔
224

225
              {isEdit && (
44✔
226
                <StyledTableHeaderCell
35✔
227
                  id="header-remove"
35✔
228
                  sx={{ textAlign: "center" }}
35✔
229
                  data-testid="header-remove"
35✔
230
                >
231
                  Remove
232
                </StyledTableHeaderCell>
35✔
233
              )}
234
            </StyledTableHeaderRow>
44✔
235
          </TableHead>
44✔
236
          <TableBody>
44✔
237
            {currentCollaborators?.map((collaborator, idx) => (
44✔
238
              <StyledTableRow
27✔
239
                // eslint-disable-next-line react/no-array-index-key
240
                key={`collaborator_${idx}_${collaborator.collaboratorID}`}
27✔
241
                data-testid={`collaborator-row-${idx}`}
27✔
242
              >
243
                <StyledNameCell>
27✔
244
                  <StyledSelect
27✔
245
                    value={collaborator.collaboratorID || ""}
27✔
246
                    onChange={(e) =>
27✔
247
                      handleUpdateCollaborator(idx, {
1✔
248
                        collaboratorID: e?.target?.value as string,
1✔
249
                        permission: collaborator.permission,
1✔
250
                      })
1✔
251
                    }
252
                    autoFocus={isEdit}
27✔
253
                    placeholderText="Select Name"
27✔
254
                    MenuProps={{ disablePortal: true }}
27✔
255
                    data-testid={`collaborator-select-${idx}`}
27✔
256
                    inputProps={{
27✔
257
                      "data-testid": `collaborator-select-${idx}-input`,
27✔
258
                      "aria-labelledby": "header-collaborator",
27✔
259
                    }}
27✔
260
                    renderValue={() => (
27✔
261
                      <TruncatedText
44✔
262
                        text={collaborator.collaboratorName ?? " "}
44✔
263
                        maxCharacters={20}
44✔
264
                        underline={false}
44✔
265
                        ellipsis
44✔
266
                      />
267
                    )}
268
                    readOnly={loading || !isEdit}
27✔
269
                    required={currentCollaborators?.length > 1}
27✔
270
                    aria-label="Collaborator dropdown"
27✔
271
                  >
272
                    {[collaborator, ...remainingPotentialCollaborators]
27✔
273
                      ?.filter((collaborator) => !!collaborator?.collaboratorID)
27✔
274
                      ?.sort((a, b) => a.collaboratorName?.localeCompare(b.collaboratorName))
27✔
275
                      ?.map((c) => (
27✔
276
                        <MenuItem key={c.collaboratorID} value={c.collaboratorID}>
62✔
277
                          {c.collaboratorName}
62✔
278
                        </MenuItem>
62✔
279
                      ))}
27✔
280
                  </StyledSelect>
27✔
281
                </StyledNameCell>
27✔
282

283
                <StyledTableCell data-testid={`collaborator-access-${idx}`}>
27✔
284
                  <Stack direction="row" justifyContent="center" alignItems="center">
27✔
285
                    <StyledRadioGroup
27✔
286
                      value={collaborator?.permission || ""}
27✔
287
                      onChange={(e, val: CollaboratorPermissions) =>
27✔
NEW
UNCOV
288
                        handleUpdateCollaborator(idx, {
×
NEW
UNCOV
289
                          collaboratorID: collaborator?.collaboratorID,
×
NEW
UNCOV
290
                          permission: val,
×
NEW
UNCOV
291
                        })
×
292
                      }
293
                      data-testid={`collaborator-permissions-${idx}`}
27✔
294
                      aria-labelledby="header-access"
27✔
295
                      row
27✔
296
                    >
297
                      <StyledRadioControl
27✔
298
                        value="Can Edit"
27✔
299
                        control={
27✔
300
                          <StyledRadioButton
27✔
301
                            readOnly={loading || !isEdit}
27✔
302
                            disabled={loading || !isEdit}
27✔
303
                            required
27✔
304
                          />
305
                        }
306
                        label="Can Edit"
27✔
307
                      />
308

309
                      <StyledRadioControl
27✔
310
                        value="No Access"
27✔
311
                        control={
27✔
312
                          <StyledRadioButton
27✔
313
                            readOnly={loading || !isEdit}
27✔
314
                            disabled={loading || !isEdit}
27✔
315
                            required
27✔
316
                          />
317
                        }
318
                        label="No Access"
27✔
319
                      />
320
                    </StyledRadioGroup>
27✔
321
                  </Stack>
27✔
322
                </StyledTableCell>
27✔
323

324
                {isEdit && (
27✔
325
                  <StyledTableCell>
23✔
326
                    <Stack direction="row" justifyContent="center" alignItems="center">
23✔
327
                      <StyledRemoveButton
23✔
328
                        onClick={() => handleRemoveCollaborator(idx)}
23✔
329
                        disabled={loading}
23✔
330
                        data-testid={`remove-collaborator-button-${idx}`}
23✔
331
                        aria-label="Remove row"
23✔
332
                      >
333
                        <RemoveIconSvg />
23✔
334
                      </StyledRemoveButton>
23✔
335
                    </Stack>
23✔
336
                  </StyledTableCell>
23✔
337
                )}
338
              </StyledTableRow>
27✔
339
            ))}
44✔
340
          </TableBody>
44✔
341
        </FixedTable>
44✔
342
      </StyledTableContainer>
44✔
343

344
      <AddRemoveButton
44✔
345
        id="add-collaborator-button"
44✔
346
        label="Add Collaborator"
44✔
347
        placement="start"
44✔
348
        startIcon={<AddCircleIcon />}
44✔
349
        onClick={handleAddCollaborator}
44✔
350
        disabled={loading || !isEdit || currentCollaborators?.length >= maxCollaborators}
44✔
351
        aria-label="Add Collaborator"
44✔
352
        tooltipProps={{
44✔
353
          placement: "top",
44✔
354
          title: TOOLTIP_TEXT.COLLABORATORS_DIALOG.ACTIONS.ADD_COLLABORATOR_DISABLED,
44✔
355
          disableHoverListener: isEdit && currentCollaborators?.length < maxCollaborators,
44✔
356
          disableInteractive: true,
44✔
357
        }}
44✔
358
        data-testid="add-collaborator-button"
44✔
359
      />
360
    </>
44✔
361
  );
362
};
44✔
363

364
export default React.memo(CollaboratorsTable, isEqual);
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