• 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

71.43
/src/js/components/app.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 { useIdleTimer, workerTimers } from 'react-idle-timer';
16
import { connect } from 'react-redux';
17
import { useLocation, useNavigate } from 'react-router-dom';
18

19
import { CssBaseline } from '@mui/material';
20
import { ThemeProvider, createTheme } from '@mui/material/styles';
21
import withStyles from '@mui/styles/withStyles';
22
import { makeStyles } from 'tss-react/mui';
23

24
import Cookies from 'universal-cookie';
25

26
import { parseEnvironmentInfo, setSnackbar } from '../actions/appActions';
27
import { cancelFileUpload } from '../actions/releaseActions';
28
import { logoutUser, saveUserSettings, setAccountActivationCode, setShowConnectingDialog } from '../actions/userActions';
29
import { expirySet, getToken, updateMaxAge } from '../auth';
30
import SharedSnackbar from '../components/common/sharedsnackbar';
31
import { PrivateRoutes, PublicRoutes } from '../config/routes';
32
import { onboardingSteps } from '../constants/onboardingConstants';
33
import ErrorBoundary from '../errorboundary';
34
import { toggle } from '../helpers';
35
import { getOnboardingState, getUserSettings } from '../selectors';
36
import { dark as darkTheme, light as lightTheme } from '../themes/Mender';
37
import Tracking from '../tracking';
38
import { getOnboardingComponentFor } from '../utils/onboardingmanager';
39
import ConfirmDismissHelptips from './common/dialogs/confirmdismisshelptips';
40
import DeviceConnectionDialog from './common/dialogs/deviceconnectiondialog';
41
import Footer from './footer';
42
import Header from './header/header';
43
import LeftNav from './leftnav';
44
import SearchResult from './search-result';
45
import Uploads from './uploads';
46

47
const activationPath = '/activate';
2✔
48
export const timeout = 900000; // 15 minutes idle time
2✔
49
const cookies = new Cookies();
2✔
50

51
const reducePalette =
52
  prefix =>
2✔
53
  (accu, [key, value]) => {
736✔
54
    if (value instanceof Object) {
3,136✔
55
      return {
704✔
56
        ...accu,
57
        ...Object.entries(value).reduce(reducePalette(`${prefix}-${key}`), {})
58
      };
59
    } else {
60
      accu[`${prefix}-${key}`] = value;
2,432✔
61
    }
62
    return accu;
2,432✔
63
  };
64

65
const cssVariables = ({ palette }) => {
2✔
66
  const muiVariables = Object.entries(palette).reduce(reducePalette('--mui'), {});
32✔
67
  return {
32✔
68
    '@global': {
69
      ':root': {
70
        ...muiVariables,
71
        '--mui-overlay': palette.grey[400]
72
      }
73
    }
74
  };
75
};
76

77
const WrappedBaseline = withStyles(cssVariables)(CssBaseline);
2✔
78

79
const useStyles = makeStyles()(() => ({
9✔
80
  public: {
81
    display: 'grid',
82
    gridTemplateRows: 'max-content 1fr max-content',
83
    height: '100vh',
84
    '.content': {
85
      alignSelf: 'center',
86
      justifySelf: 'center'
87
    }
88
  }
89
}));
90

91
export const AppRoot = ({
2✔
92
  currentUser,
93
  logoutUser,
94
  mode,
95
  onboardingState,
96
  parseEnvironmentInfo,
97
  setAccountActivationCode,
98
  setShowConnectingDialog,
99
  showDeviceConnectionDialog,
100
  showDismissHelptipsDialog,
101
  setSnackbar,
102
  snackbar,
103
  trackingCode
104
}) => {
105
  const [showSearchResult, setShowSearchResult] = useState(false);
51✔
106
  const navigate = useNavigate();
51✔
107
  const { pathname = '', hash } = useLocation();
51!
108

109
  useEffect(() => {
51✔
110
    parseEnvironmentInfo();
8✔
111
    if (!trackingCode) {
8✔
112
      return;
5✔
113
    }
114
    if (!cookies.get('_ga')) {
3!
115
      Tracking.cookieconsent().then(({ trackingConsentGiven }) => {
×
116
        if (trackingConsentGiven) {
×
117
          Tracking.initialize(trackingCode);
×
118
          Tracking.pageview();
×
119
        }
120
      });
121
    } else {
122
      Tracking.initialize(trackingCode);
3✔
123
    }
124
    trackLocationChange(pathname);
3✔
125
  }, [trackingCode]);
126

127
  useEffect(() => {
51✔
128
    trackLocationChange(pathname);
6✔
129
    // the following is added to ensure backwards capability for hash containing routes & links (e.g. /ui/#/devices => /ui/devices)
130
    if (hash) {
6!
131
      navigate(hash.substring(1));
×
132
    }
133
  }, [hash, pathname]);
134

135
  const trackLocationChange = pathname => {
51✔
136
    let page = pathname;
9✔
137
    // if we're on page whose path might contain sensitive device/ group/ deployment names etc. we sanitize the sent information before submission
138
    if (page.includes('=') && (page.startsWith('/devices') || page.startsWith('/deployments'))) {
9!
139
      const splitter = page.lastIndexOf('/');
×
140
      const filters = page.slice(splitter + 1);
×
141
      const keyOnlyFilters = filters.split('&').reduce((accu, item) => `${accu}:${item.split('=')[0]}&`, ''); // assume the keys to filter by are not as revealing as the values things are filtered by
×
142
      page = `${page.substring(0, splitter)}?${keyOnlyFilters.substring(0, keyOnlyFilters.length - 1)}`; // cut off the last & of the reduced filters string
×
143
    } else if (page.startsWith(activationPath)) {
9!
144
      setAccountActivationCode(page.substring(activationPath.length + 1));
×
145
      navigate('/settings/my-profile', { replace: true });
×
146
    }
147
    Tracking.pageview(page);
9✔
148
  };
149

150
  const onIdle = () => {
51✔
151
    if (expirySet() && currentUser) {
2!
152
      // logout user and warn
153
      return logoutUser('Your session has expired. You have been automatically logged out due to inactivity.').catch(updateMaxAge);
2✔
154
    }
155
  };
156

157
  useIdleTimer({ crossTab: true, onAction: updateMaxAge, onActive: updateMaxAge, onIdle, syncTimers: 400, timeout, timers: workerTimers });
51✔
158

159
  const onToggleSearchResult = () => setShowSearchResult(toggle);
51✔
160

161
  const onboardingComponent = getOnboardingComponentFor(onboardingSteps.ARTIFACT_CREATION_DIALOG, onboardingState);
51✔
162
  const theme = createTheme(mode === 'dark' ? darkTheme : lightTheme);
51!
163

164
  const { classes } = useStyles();
51✔
165

166
  return (
51✔
167
    <ThemeProvider theme={theme}>
168
      <WrappedBaseline enableColorScheme />
169
      <>
170
        {getToken() ? (
51✔
171
          <div id="app">
172
            <Header mode={mode} />
173
            <LeftNav />
174
            <div className="rightFluid container">
175
              <ErrorBoundary>
176
                <SearchResult onToggleSearchResult={onToggleSearchResult} open={showSearchResult} />
177
                <PrivateRoutes />
178
              </ErrorBoundary>
179
            </div>
180
            {onboardingComponent ? onboardingComponent : null}
49!
181
            {showDismissHelptipsDialog && <ConfirmDismissHelptips />}
49!
182
            {showDeviceConnectionDialog && <DeviceConnectionDialog onCancel={() => setShowConnectingDialog(false)} />}
×
183
          </div>
184
        ) : (
185
          <div className={classes.public}>
186
            <PublicRoutes />
187
            <Footer />
188
          </div>
189
        )}
190
        <SharedSnackbar snackbar={snackbar} setSnackbar={setSnackbar} />
191
        <Uploads />
192
      </>
193
    </ThemeProvider>
194
  );
195
};
196

197
const actionCreators = { cancelFileUpload, logoutUser, parseEnvironmentInfo, saveUserSettings, setAccountActivationCode, setShowConnectingDialog, setSnackbar };
2✔
198

199
const mapStateToProps = state => {
2✔
200
  return {
449✔
201
    currentUser: state.users.currentUser,
202
    onboardingState: getOnboardingState(state),
203
    showDismissHelptipsDialog: !state.onboarding.complete && state.onboarding.showTipsDialog,
868✔
204
    showDeviceConnectionDialog: state.users.showConnectDeviceDialog,
205
    snackbar: state.app.snackbar,
206
    trackingCode: state.app.trackerCode,
207
    mode: getUserSettings(state).mode
208
  };
209
};
210

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