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

mendersoftware / gui / 1493849842

13 Oct 2024 07:39AM UTC coverage: 83.457% (-16.5%) from 99.965%
1493849842

Pull #4531

gitlab-ci

web-flow
chore: Bump send and express in /tests/e2e_tests

Bumps [send](https://github.com/pillarjs/send) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `send` from 0.18.0 to 0.19.0
- [Release notes](https://github.com/pillarjs/send/releases)
- [Changelog](https://github.com/pillarjs/send/blob/master/HISTORY.md)
- [Commits](https://github.com/pillarjs/send/compare/0.18.0...0.19.0)

Updates `express` from 4.19.2 to 4.21.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.1)

---
updated-dependencies:
- dependency-name: send
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #4531: chore: Bump send and express in /tests/e2e_tests

4486 of 6422 branches covered (69.85%)

8551 of 10246 relevant lines covered (83.46%)

151.3 hits per line

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

96.36
/src/js/components/settings/user-management/userdefinition.js
1
// Copyright 2021 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14
import React, { useEffect, useMemo, useState } from 'react';
15

16
// material ui
17
import { Close as CloseIcon } from '@mui/icons-material';
18
import {
19
  Button,
20
  Checkbox,
21
  Divider,
22
  Drawer,
23
  FormControl,
24
  FormControlLabel,
25
  FormHelperText,
26
  IconButton,
27
  InputLabel,
28
  TextField,
29
  textFieldClasses
30
} from '@mui/material';
31
import { makeStyles } from 'tss-react/mui';
32

33
import validator from 'validator';
34

35
import { mapUserRolesToUiPermissions } from '../../../actions/userActions';
36
import { uiPermissionsByArea, uiPermissionsById } from '../../../constants/userConstants';
37
import { toggle } from '../../../helpers';
38
import { TwoColumnData } from '../../common/configurationobject';
39
import { OAuth2Providers, genericProvider } from '../../login/oauth2providers';
40
import { CopyTextToClipboard } from '../organization/organization';
41
import { UserRolesSelect } from './userform';
42

43
const useStyles = makeStyles()(theme => ({
9✔
44
  actionButtons: { justifyContent: 'flex-end' },
45
  divider: { marginTop: theme.spacing(4) },
46
  leftButton: { marginRight: theme.spacing(2) },
47
  oauthIcon: { fontSize: 36, marginRight: 10 },
48
  userId: { marginTop: theme.spacing(3) },
49
  userIdWrapper: {
50
    // the following 2 lines are required to align the CopyTextToClipboard with the tenant token without sacrificing consistent behaviour
51
    marginBottom: theme.spacing(-3),
52
    '.copy-button': { marginTop: theme.spacing(3) },
53
    [`.${textFieldClasses.root}`]: { width: 400 },
54
    maxWidth: 600
55
  },
56
  widthLimit: { maxWidth: 400 }
57
}));
58

59
export const getUserSSOState = user => {
5✔
60
  const { sso = [] } = user;
55✔
61
  const isOAuth2 = !!sso.length;
55✔
62
  let provider = null;
55✔
63
  if (isOAuth2) {
55✔
64
    provider = OAuth2Providers.find(provider => sso.some(({ kind }) => kind.includes(provider.id))) ?? genericProvider;
4!
65
  }
66
  return { isOAuth2, provider };
55✔
67
};
68

69
const mapPermissions = permissions => permissions.map(permission => uiPermissionsById[permission].title).join(', ');
60✔
70

71
const scopedPermissionAreas = ['groups', 'releases'];
5✔
72

73
export const UserId = ({ className = '', userId }) => {
5!
74
  const { classes } = useStyles();
44✔
75
  return (
44✔
76
    <div className={`flexbox space-between ${classes.userIdWrapper} ${className}`}>
77
      <TextField label="User ID" key={userId} InputLabelProps={{ shrink: !!userId }} disabled defaultValue={userId} />
78
      <div className="flexbox center-aligned copy-button">
79
        <CopyTextToClipboard token={userId} />
80
      </div>
81
    </div>
82
  );
83
};
84

85
export const UserDefinition = ({ currentUser, isEnterprise, onCancel, onSubmit, onRemove, roles, selectedUser }) => {
5✔
86
  const { email = '', id } = selectedUser;
44✔
87

88
  const { classes } = useStyles();
44✔
89

90
  const [nameError, setNameError] = useState(false);
44✔
91
  const [hadRoleChanges, setHadRoleChanges] = useState(false);
44✔
92
  const [selectedRoles, setSelectedRoles] = useState([]);
44✔
93
  const [shouldResetPassword, setShouldResetPassword] = useState(false);
44✔
94
  const [currentEmail, setCurrentEmail] = useState('');
44✔
95

96
  useEffect(() => {
44✔
97
    setCurrentEmail(email);
9✔
98
  }, [email]);
99

100
  useEffect(() => {
44✔
101
    setSelectedRoles(selectedUser.roles || []);
9✔
102
  }, [selectedUser.roles]);
103

104
  const validateNameChange = ({ target: { value } }) => {
44✔
105
    setNameError(!validator.isEmail(value) || validator.isEmpty(value));
14✔
106
    setCurrentEmail(value);
14✔
107
  };
108

109
  const onRemoveClick = () => {
44✔
110
    onRemove(selectedUser);
1✔
111
  };
112

113
  const onRolesSelect = (newlySelectedRoles, hadRoleChanges) => {
44✔
114
    setSelectedRoles(newlySelectedRoles);
4✔
115
    setHadRoleChanges(hadRoleChanges);
4✔
116
  };
117

118
  const onSubmitClick = () => {
44✔
119
    if (id && !hadRoleChanges && email === currentEmail) {
1!
120
      return onSubmit(null, 'edit', id, shouldResetPassword ? email : null);
×
121
    }
122
    const changedRoles = hadRoleChanges ? { roles: selectedRoles } : {};
1!
123
    const submissionData = { ...selectedUser, ...changedRoles, email: currentEmail };
1✔
124
    return onSubmit(submissionData, 'edit', id, shouldResetPassword ? currentEmail : null);
1!
125
  };
126

127
  const togglePasswordReset = () => setShouldResetPassword(toggle);
44✔
128

129
  const { areas, groups } = useMemo(() => {
44✔
130
    const emptySelection = { areas: {}, groups: {}, releases: {} };
17✔
131
    if (!(selectedRoles && roles)) {
17!
132
      return emptySelection;
×
133
    }
134

135
    return Object.entries(mapUserRolesToUiPermissions(selectedRoles, roles)).reduce((accu, [key, values]) => {
17✔
136
      if (scopedPermissionAreas.includes(key)) {
85✔
137
        accu[key] = Object.entries(values).reduce((groupsAccu, [name, uiPermissions]) => {
34✔
138
          groupsAccu[name] = mapPermissions(uiPermissions);
9✔
139
          return groupsAccu;
9✔
140
        }, {});
141
      } else {
142
        accu.areas[uiPermissionsByArea[key].title] = mapPermissions(values);
51✔
143
      }
144
      return accu;
85✔
145
    }, emptySelection);
146
  }, [selectedRoles, roles]);
147

148
  const isSubmitDisabled = !selectedRoles.length;
44✔
149

150
  const { isOAuth2, provider } = getUserSSOState(selectedUser);
44✔
151
  const rolesClasses = isEnterprise ? '' : 'muted';
44!
152
  return (
44✔
153
    <Drawer anchor="right" open={!!id} PaperProps={{ style: { minWidth: 600, width: '50vw' } }}>
154
      <div className="flexbox margin-bottom-small space-between">
155
        <h3>Edit user</h3>
156
        <div className="flexbox center-aligned">
157
          {currentUser.id !== id && (
88✔
158
            <Button className={`flexbox center-aligned ${classes.leftButton}`} color="secondary" onClick={onRemoveClick}>
159
              delete user
160
            </Button>
161
          )}
162
          <IconButton onClick={onCancel} aria-label="close">
163
            <CloseIcon />
164
          </IconButton>
165
        </div>
166
      </div>
167
      <Divider />
168
      <UserId className={classes.userId} userId={id} />
169
      <FormControl className={`${classes.widthLimit} margin-top-none`}>
170
        <TextField label="Email" id="email" value={currentEmail} disabled={isOAuth2 || currentUser.id === id} error={nameError} onChange={validateNameChange} />
88✔
171
        {nameError && <FormHelperText className="warning">Please enter a valid email address</FormHelperText>}
56✔
172
      </FormControl>
173
      {isOAuth2 ? (
44!
174
        <div className="flexbox margin-top-small margin-bottom">
175
          <div className={classes.oauthIcon}>{provider.icon}</div>
176
          <div className="info">
177
            This user logs in using their <strong>{provider.name}</strong> account.
178
            <br />
179
            They can connect to {provider.name} to update their login settings.
180
          </div>
181
        </div>
182
      ) : (
183
        <FormControlLabel
184
          control={<Checkbox checked={shouldResetPassword} onChange={togglePasswordReset} />}
185
          label="Send an email to the user containing a link to reset the password"
186
        />
187
      )}
188
      <UserRolesSelect disabled={!isEnterprise} currentUser={currentUser} onSelect={onRolesSelect} roles={roles} user={selectedUser} />
189
      {!!(Object.keys(groups).length || Object.keys(areas).length) && (
151✔
190
        <InputLabel className="margin-top" shrink>
191
          Role permissions
192
        </InputLabel>
193
      )}
194
      <TwoColumnData className={rolesClasses} config={areas} />
195
      {!!Object.keys(groups).length && (
69✔
196
        <>
197
          <InputLabel className="margin-top-small" shrink>
198
            Device groups
199
          </InputLabel>
200
          <TwoColumnData className={rolesClasses} config={groups} />
201
        </>
202
      )}
203
      <Divider className={classes.divider} light />
204
      <div className={`flexbox centered margin-top ${classes.actionButtons}`}>
205
        <Button className={classes.leftButton} onClick={onCancel}>
206
          Cancel
207
        </Button>
208
        <Button color="secondary" variant="contained" disabled={isSubmitDisabled} target="_blank" onClick={onSubmitClick}>
209
          Save
210
        </Button>
211
      </div>
212
    </Drawer>
213
  );
214
};
215

216
export default UserDefinition;
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