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

mendersoftware / gui / 897326496

pending completion
897326496

Pull #3752

gitlab-ci

mzedel
chore(e2e): made use of shared timeout & login checking values to remove code duplication

Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #3752: chore(e2e-tests): slightly simplified log in test + separated log out test

4395 of 6392 branches covered (68.76%)

8060 of 9780 relevant lines covered (82.41%)

126.17 hits per line

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

74.29
/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 { connect } 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, setSnackbar } 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, isEmpty } from '../../helpers';
41
import { getDocsVersion, getIsEnterprise, getUserCapabilities, getUserSettings } from '../../selectors';
42
import Tracking from '../../tracking';
43
import { useDebounce } from '../../utils/debouncehook';
44
import Search from '../common/search';
45
import Announcement from './announcement';
46
import DemoNotification from './demonotification';
47
import DeploymentNotifications from './deploymentnotifications';
48
import DeviceNotifications from './devicenotifications';
49
import OfferHeader from './offerheader';
50
import TrialNotification from './trialnotification';
51

52
// Change this when a new feature/offer is introduced
53
const currentOffer = {
3✔
54
  name: 'add-ons',
55
  expires: '2021-12-30',
56
  trial: true,
57
  os: true,
58
  professional: true,
59
  enterprise: true
60
};
61

62
const cookies = new Cookies();
3✔
63

64
const useStyles = makeStyles()(theme => ({
51✔
65
  header: {
66
    minHeight: 'unset',
67
    paddingLeft: theme.spacing(4),
68
    paddingRight: theme.spacing(5),
69
    width: '100%',
70
    borderBottom: `1px solid ${theme.palette.grey[100]}`,
71
    display: 'grid'
72
  },
73
  banner: { gridTemplateRows: `1fr ${theme.mixins.toolbar.minHeight}px` },
74
  buttonColor: { color: theme.palette.grey[600] },
75
  dropDown: { height: '100%', marginLeft: theme.spacing(0.5), textTransform: 'none' },
76
  exitIcon: { color: theme.palette.grey[600], fill: theme.palette.grey[600] },
77
  demoTrialAnnouncement: {
78
    fontSize: 14,
79
    height: 'auto'
80
  },
81
  demoAnnouncementIcon: {
82
    height: 16,
83
    color: theme.palette.primary.main,
84
    '&.MuiButton-textPrimary': {
85
      color: theme.palette.primary.main,
86
      height: 'inherit'
87
    }
88
  },
89
  redAnnouncementIcon: {
90
    color: theme.palette.error.dark
91
  }
92
}));
93

94
export const Header = ({
3✔
95
  acceptedDevices,
96
  allowUserManagement,
97
  announcement,
98
  demo,
99
  deviceLimit,
100
  docsVersion,
101
  firstLoginAfterSignup,
102
  getOnboardingState,
103
  getUser,
104
  hasTrackingEnabled,
105
  initializeAppData,
106
  inProgress,
107
  isEnterprise,
108
  isHosted,
109
  isSearching,
110
  logoutUser,
111
  mode,
112
  multitenancy,
113
  organization,
114
  pendingDevices,
115
  searchTerm,
116
  setFirstLoginAfterSignup,
117
  setHideAnnouncement,
118
  setSearchState,
119
  showHelptips,
120
  toggleHelptips,
121
  user
122
}) => {
123
  const { classes } = useStyles();
440✔
124
  const [anchorEl, setAnchorEl] = useState(null);
440✔
125
  const [loggingOut, setLoggingOut] = useState(false);
440✔
126
  const [gettingUser, setGettingUser] = useState(false);
440✔
127
  const [hasOfferCookie, setHasOfferCookie] = useState(false);
440✔
128

129
  const sessionId = useDebounce(getToken(), TIMEOUTS.debounceDefault);
440✔
130

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

146
  useEffect(() => {
440✔
147
    // updateUsername();
148
    const showOfferCookie = cookies.get('offer') === currentOffer.name;
8✔
149
    setHasOfferCookie(showOfferCookie);
8✔
150
  }, []);
151

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

169
  const onLogoutClick = () => {
440✔
170
    setGettingUser(false);
1✔
171
    setLoggingOut(true);
1✔
172
    setAnchorEl(null);
1✔
173
    logoutUser();
1✔
174
  };
175

176
  const onSearch = searchTerm => setSearchState({ searchTerm, page: 1 });
440✔
177

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

183
  const showOffer =
184
    isHosted && moment().isBefore(currentOffer.expires) && (organization.trial ? currentOffer.trial : currentOffer[organization.plan]) && !hasOfferCookie;
440!
185

186
  const headerLogo = mode === 'dark' ? (isEnterprise ? whiteEnterpriseLogo : whiteLogo) : isEnterprise ? enterpriseLogo : logo;
440!
187

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

274
const actionCreators = {
3✔
275
  getOnboardingState,
276
  getUser,
277
  setHideAnnouncement,
278
  initializeAppData,
279
  logoutUser,
280
  setFirstLoginAfterSignup,
281
  setSnackbar,
282
  setSearchState,
283
  toggleHelptips
284
};
285

286
const mapStateToProps = state => {
3✔
287
  const organization = !isEmpty(state.organization.organization) ? state.organization.organization : { plan: 'os', id: null };
433✔
288
  const { canManageUsers: allowUserManagement } = getUserCapabilities(state);
433✔
289
  return {
433✔
290
    acceptedDevices: state.devices.byStatus.accepted.total,
291
    allowUserManagement,
292
    announcement: state.app.hostedAnnouncement,
293
    deviceLimit: state.devices.limit,
294
    demo: state.app.features.isDemoMode,
295
    docsVersion: getDocsVersion(state),
296
    firstLoginAfterSignup: state.app.firstLoginAfterSignup,
297
    hasTrackingEnabled: getUserSettings(state).trackingConsentGiven,
298
    inProgress: state.deployments.byStatus.inprogress.total,
299
    isEnterprise: getIsEnterprise(state),
300
    isHosted: state.app.features.isHosted,
301
    isSearching: state.app.searchState.isSearching,
302
    multitenancy: state.app.features.hasMultitenancy || state.app.features.isEnterprise || state.app.features.isHosted,
457✔
303
    searchTerm: state.app.searchState.searchTerm,
304
    showHelptips: state.users.showHelptips,
305
    pendingDevices: state.devices.byStatus.pending.total,
306
    organization,
307
    user: state.users.byId[state.users.currentUser] || { email: '', id: null }
727✔
308
  };
309
};
310

311
export default connect(mapStateToProps, actionCreators)(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