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

mendersoftware / gui / 914712491

pending completion
914712491

Pull #3798

gitlab-ci

mzedel
refac: refactored signup page to make better use of form capabilities

Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #3798: MEN-3530 - refactored forms

4359 of 6322 branches covered (68.95%)

92 of 99 new or added lines in 11 files covered. (92.93%)

1715 existing lines in 159 files now uncovered.

8203 of 9941 relevant lines covered (82.52%)

150.06 hits per line

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

74.79
/src/js/components/header/header.js
1
// Copyright 2015 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, useState } from 'react';
15
import { useDispatch, useSelector } from 'react-redux';
16
import { Link } from 'react-router-dom';
17

18
import {
19
  AccountCircle as AccountCircleIcon,
20
  ArrowDropDown as ArrowDropDownIcon,
21
  ArrowDropUp as ArrowDropUpIcon,
22
  ExitToApp as ExitIcon
23
} from '@mui/icons-material';
24
import { Button, IconButton, ListItemSecondaryAction, ListItemText, Menu, MenuItem, Toolbar } from '@mui/material';
25
import { makeStyles } from 'tss-react/mui';
26

27
import moment from 'moment';
28
import Cookies from 'universal-cookie';
29

30
import enterpriseLogo from '../../../assets/img/headerlogo-enterprise.png';
31
import logo from '../../../assets/img/headerlogo.png';
32
import whiteEnterpriseLogo from '../../../assets/img/whiteheaderlogo-enterprise.png';
33
import whiteLogo from '../../../assets/img/whiteheaderlogo.png';
34
import { initializeAppData, setFirstLoginAfterSignup, setSearchState } from '../../actions/appActions';
35
import { getOnboardingState } from '../../actions/onboardingActions';
36
import { getUser, logoutUser, setHideAnnouncement, toggleHelptips } from '../../actions/userActions';
37
import { getToken } from '../../auth';
38
import { TIMEOUTS } from '../../constants/appConstants';
39
import * as UserConstants from '../../constants/userConstants';
40
import { decodeSessionToken, extractErrorMessage } from '../../helpers';
41
import {
42
  getAcceptedDevices,
43
  getCurrentUser,
44
  getDeviceCountsByStatus,
45
  getDeviceLimit,
46
  getDocsVersion,
47
  getFeatures,
48
  getIsEnterprise,
49
  getOrganization,
50
  getShowHelptips,
51
  getUserCapabilities,
52
  getUserSettings
53
} from '../../selectors';
54
import Tracking from '../../tracking';
55
import { useDebounce } from '../../utils/debouncehook';
56
import Search from '../common/search';
57
import Announcement from './announcement';
58
import DemoNotification from './demonotification';
59
import DeploymentNotifications from './deploymentnotifications';
60
import DeviceNotifications from './devicenotifications';
61
import OfferHeader from './offerheader';
62
import TrialNotification from './trialnotification';
63

64
// Change this when a new feature/offer is introduced
65
const currentOffer = {
3✔
66
  name: 'add-ons',
67
  expires: '2021-12-30',
68
  trial: true,
69
  os: true,
70
  professional: true,
71
  enterprise: true
72
};
73

74
const cookies = new Cookies();
3✔
75

76
const useStyles = makeStyles()(theme => ({
57✔
77
  header: {
78
    minHeight: 'unset',
79
    paddingLeft: theme.spacing(4),
80
    paddingRight: theme.spacing(5),
81
    width: '100%',
82
    borderBottom: `1px solid ${theme.palette.grey[100]}`,
83
    display: 'grid'
84
  },
85
  banner: { gridTemplateRows: `1fr ${theme.mixins.toolbar.minHeight}px` },
86
  buttonColor: { color: theme.palette.grey[600] },
87
  dropDown: { height: '100%', marginLeft: theme.spacing(0.5), textTransform: 'none' },
88
  exitIcon: { color: theme.palette.grey[600], fill: theme.palette.grey[600] },
89
  demoTrialAnnouncement: {
90
    fontSize: 14,
91
    height: 'auto'
92
  },
93
  demoAnnouncementIcon: {
94
    height: 16,
95
    color: theme.palette.primary.main,
96
    '&.MuiButton-textPrimary': {
97
      color: theme.palette.primary.main,
98
      height: 'inherit'
99
    }
100
  },
101
  redAnnouncementIcon: {
102
    color: theme.palette.error.dark
103
  }
104
}));
105

106
export const Header = ({ mode }) => {
3✔
107
  const { classes } = useStyles();
398✔
108
  const [anchorEl, setAnchorEl] = useState(null);
398✔
109
  const [loggingOut, setLoggingOut] = useState(false);
398✔
110
  const [gettingUser, setGettingUser] = useState(false);
398✔
111
  const [hasOfferCookie, setHasOfferCookie] = useState(false);
398✔
112
  const sessionId = useDebounce(getToken(), TIMEOUTS.debounceDefault);
398✔
113

114
  const organization = useSelector(getOrganization);
398✔
115
  const { canManageUsers: allowUserManagement } = useSelector(getUserCapabilities);
398✔
116
  const { total: acceptedDevices = 0 } = useSelector(getAcceptedDevices);
398!
117
  const announcement = useSelector(state => state.app.hostedAnnouncement);
828✔
118
  const deviceLimit = useSelector(getDeviceLimit);
398✔
119
  const docsVersion = useSelector(getDocsVersion);
398✔
120
  const firstLoginAfterSignup = useSelector(state => state.app.firstLoginAfterSignup);
828✔
121
  const { trackingConsentGiven: hasTrackingEnabled } = useSelector(getUserSettings);
398✔
122
  const inProgress = useSelector(state => state.deployments.byStatus.inprogress.total);
828✔
123
  const isEnterprise = useSelector(getIsEnterprise);
398✔
124
  const { isDemoMode: demo, hasMultitenancy, isHosted } = useSelector(getFeatures);
398✔
125
  const isSearching = useSelector(state => state.app.searchState.isSearching);
828✔
126
  const multitenancy = hasMultitenancy || isEnterprise || isHosted;
398✔
127
  const searchTerm = useSelector(state => state.app.searchState.searchTerm);
828✔
128
  const showHelptips = useSelector(getShowHelptips);
398✔
129
  const { pending: pendingDevices } = useSelector(getDeviceCountsByStatus);
398✔
130
  const user = useSelector(getCurrentUser);
398✔
131
  const dispatch = useDispatch();
398✔
132

133
  useEffect(() => {
398✔
134
    if ((!sessionId || !user?.id || !user.email.length) && !gettingUser && !loggingOut) {
12✔
135
      updateUsername();
6✔
136
      return;
6✔
137
    }
138
    Tracking.setTrackingEnabled(hasTrackingEnabled);
6✔
139
    if (hasTrackingEnabled && user.id && organization.id) {
6!
UNCOV
140
      Tracking.setOrganizationUser(organization, user);
×
UNCOV
141
      if (firstLoginAfterSignup) {
×
UNCOV
142
        Tracking.pageview('/signup/complete');
×
UNCOV
143
        dispatch(setFirstLoginAfterSignup(false));
×
144
      }
145
    }
146
  }, [sessionId, user.id, user.email, gettingUser, loggingOut]);
147

148
  useEffect(() => {
398✔
149
    // updateUsername();
150
    const showOfferCookie = cookies.get('offer') === currentOffer.name;
7✔
151
    setHasOfferCookie(showOfferCookie);
7✔
152
  }, []);
153

154
  const updateUsername = () => {
398✔
155
    const userId = decodeSessionToken(getToken());
6✔
156
    if (gettingUser || !userId) {
6✔
157
      return;
5✔
158
    }
159
    setGettingUser(true);
1✔
160
    // get current user
161
    return (
1✔
162
      dispatch(getUser(UserConstants.OWN_USER_ID))
163
        .then(() => dispatch(initializeAppData()))
1✔
164
        // this is allowed to fail if no user information are available
UNCOV
165
        .catch(err => console.log(extractErrorMessage(err)))
×
166
        .then(() => dispatch(getOnboardingState()))
1✔
167
        .finally(() => setGettingUser(false))
1✔
168
    );
169
  };
170

171
  const onLogoutClick = () => {
398✔
172
    setGettingUser(false);
1✔
173
    setLoggingOut(true);
1✔
174
    setAnchorEl(null);
1✔
175
    dispatch(logoutUser());
1✔
176
  };
177

178
  const onSearch = searchTerm => dispatch(setSearchState({ searchTerm, page: 1 }));
398✔
179

180
  const setHideOffer = () => {
398✔
UNCOV
181
    cookies.set('offer', currentOffer.name, { path: '/', maxAge: 2629746 });
×
UNCOV
182
    setHasOfferCookie(true);
×
183
  };
184

185
  const showOffer =
186
    isHosted && moment().isBefore(currentOffer.expires) && (organization.trial ? currentOffer.trial : currentOffer[organization.plan]) && !hasOfferCookie;
398!
187

188
  const headerLogo = mode === 'dark' ? (isEnterprise ? whiteEnterpriseLogo : whiteLogo) : isEnterprise ? enterpriseLogo : logo;
398!
189

190
  return (
398✔
191
    <Toolbar id="fixedHeader" className={showOffer ? `${classes.header} ${classes.banner}` : classes.header}>
398!
192
      {!!announcement && (
442✔
193
        <Announcement
194
          announcement={announcement}
195
          errorIconClassName={classes.redAnnouncementIcon}
196
          iconClassName={classes.demoAnnouncementIcon}
197
          sectionClassName={classes.demoTrialAnnouncement}
UNCOV
198
          onHide={() => dispatch(setHideAnnouncement(true))}
×
199
        />
200
      )}
201
      {showOffer && <OfferHeader docsVersion={docsVersion} onHide={setHideOffer} />}
398!
202
      <div className="flexbox space-between">
203
        <div className="flexbox center-aligned">
204
          <Link to="/">
205
            <img id="logo" src={headerLogo} />
206
          </Link>
207
          {demo && <DemoNotification iconClassName={classes.demoAnnouncementIcon} sectionClassName={classes.demoTrialAnnouncement} docsVersion={docsVersion} />}
398!
208
          {organization.trial && (
398!
209
            <TrialNotification
210
              expiration={organization.trial_expiration}
211
              iconClassName={classes.demoAnnouncementIcon}
212
              sectionClassName={classes.demoTrialAnnouncement}
213
            />
214
          )}
215
        </div>
216
        <Search isSearching={isSearching} searchTerm={searchTerm} onSearch={onSearch} />
217
        <div className="flexbox center-aligned">
218
          <DeviceNotifications pending={pendingDevices} total={acceptedDevices} limit={deviceLimit} />
219
          <DeploymentNotifications inprogress={inProgress} />
220
          <Button
221
            className={`header-dropdown ${classes.dropDown}`}
222
            onClick={e => setAnchorEl(e.currentTarget)}
1✔
223
            startIcon={<AccountCircleIcon className={classes.buttonColor} />}
224
            endIcon={anchorEl ? <ArrowDropUpIcon /> : <ArrowDropDownIcon />}
398✔
225
          >
226
            {user.email}
227
          </Button>
228
          <Menu
229
            anchorEl={anchorEl}
UNCOV
230
            onClose={() => setAnchorEl(null)}
×
231
            open={Boolean(anchorEl)}
232
            anchorOrigin={{
233
              vertical: 'center',
234
              horizontal: 'center'
235
            }}
236
            transformOrigin={{
237
              vertical: 'bottom',
238
              horizontal: 'center'
239
            }}
240
          >
241
            <MenuItem component={Link} to="/settings">
242
              Settings
243
            </MenuItem>
244
            <MenuItem component={Link} to="/settings/my-profile">
245
              My profile
246
            </MenuItem>
247
            {multitenancy && (
785✔
248
              <MenuItem component={Link} to="/settings/organization-and-billing">
249
                My organization
250
              </MenuItem>
251
            )}
252
            {allowUserManagement && (
479✔
253
              <MenuItem component={Link} to="/settings/user-management">
254
                User management
255
              </MenuItem>
256
            )}
UNCOV
257
            <MenuItem onClick={() => dispatch(toggleHelptips())}>{showHelptips ? 'Hide help tooltips' : 'Show help tooltips'}</MenuItem>
×
258
            <MenuItem component={Link} to="/help/get-started">
259
              Help & support
260
            </MenuItem>
261
            <MenuItem onClick={onLogoutClick}>
262
              <ListItemText primary="Log out" />
263
              <ListItemSecondaryAction>
264
                <IconButton>
265
                  <ExitIcon className={classes.exitIcon} />
266
                </IconButton>
267
              </ListItemSecondaryAction>
268
            </MenuItem>
269
          </Menu>
270
        </div>
271
      </div>
272
    </Toolbar>
273
  );
274
};
275

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