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

mendersoftware / gui / 1081664682

22 Nov 2023 02:11PM UTC coverage: 82.798% (-17.2%) from 99.964%
1081664682

Pull #4214

gitlab-ci

tranchitella
fix: Fixed the infinite page redirects when the back button is pressed

Remove the location and navigate from the useLocationParams.setValue callback
dependencies as they change the set function that is presented in other
useEffect dependencies. This happens when the back button is clicked, which
leads to the location changing infinitely.

Changelog: Title
Ticket: MEN-6847
Ticket: MEN-6796

Signed-off-by: Ihor Aleksandrychiev <ihor.aleksandrychiev@northern.tech>
Signed-off-by: Fabio Tranchitella <fabio.tranchitella@northern.tech>
Pull Request #4214: fix: Fixed the infinite page redirects when the back button is pressed

4319 of 6292 branches covered (0.0%)

8332 of 10063 relevant lines covered (82.8%)

191.0 hits per line

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

95.45
/src/js/components/deployments/inprogressdeployments.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, { useCallback, useEffect, useRef, useState } from 'react';
15
import { useDispatch, useSelector } from 'react-redux';
16

17
import { Refresh as RefreshIcon } from '@mui/icons-material';
18
import { makeStyles } from 'tss-react/mui';
19

20
import { setSnackbar } from '../../actions/appActions';
21
import { getDeploymentsByStatus, setDeploymentsState } from '../../actions/deploymentActions';
22
import { DEPLOYMENT_STATES } from '../../constants/deploymentConstants';
23
import { onboardingSteps } from '../../constants/onboardingConstants';
24
import {
25
  getDeploymentsByStatus as getDeploymentsByStatusSelector,
26
  getDeploymentsSelectionState,
27
  getDevicesById,
28
  getIdAttribute,
29
  getIsEnterprise,
30
  getMappedDeploymentSelection,
31
  getOnboardingState,
32
  getUserCapabilities
33
} from '../../selectors';
34
import { getOnboardingComponentFor } from '../../utils/onboardingmanager';
35
import useWindowSize from '../../utils/resizehook';
36
import { clearAllRetryTimers, clearRetryTimer, setRetryTimer } from '../../utils/retrytimer';
37
import LinedHeader from '../common/lined-header';
38
import Loader from '../common/loader';
39
import { defaultRefreshDeploymentsLength as refreshDeploymentsLength } from './deployments';
40
import DeploymentsList from './deploymentslist';
41

42
export const minimalRefreshDeploymentsLength = 2000;
6✔
43

44
const useStyles = makeStyles()(theme => ({
8✔
45
  deploymentsPending: {
46
    borderColor: 'rgba(0, 0, 0, 0.06)',
47
    backgroundColor: theme.palette.background.light,
48
    color: theme.palette.text.primary,
49
    ['.dashboard-header span']: {
50
      backgroundColor: theme.palette.background.light,
51
      color: theme.palette.text.primary
52
    },
53
    ['.MuiButtonBase-root']: {
54
      color: theme.palette.text.primary
55
    }
56
  }
57
}));
58

59
export const Progress = ({ abort, createClick, ...remainder }) => {
6✔
60
  const { canConfigure, canDeploy } = useSelector(getUserCapabilities);
321✔
61
  const { attribute: idAttribute } = useSelector(getIdAttribute);
321✔
62
  const onboardingState = useSelector(getOnboardingState);
321✔
63
  const isEnterprise = useSelector(getIsEnterprise);
321✔
64
  const {
65
    finished: { total: pastDeploymentsCount },
66
    pending: { total: pendingCount },
67
    inprogress: { total: progressCount }
68
  } = useSelector(getDeploymentsByStatusSelector);
321✔
69
  const progress = useSelector(state => getMappedDeploymentSelection(state, DEPLOYMENT_STATES.inprogress));
561✔
70
  const pending = useSelector(state => getMappedDeploymentSelection(state, DEPLOYMENT_STATES.pending));
561✔
71
  const selectionState = useSelector(getDeploymentsSelectionState);
321✔
72
  const devices = useSelector(getDevicesById);
321✔
73
  const dispatch = useDispatch();
321✔
74
  const dispatchedSetSnackbar = useCallback((...args) => dispatch(setSnackbar(...args)), [dispatch]);
321✔
75

76
  const { page: progressPage, perPage: progressPerPage } = selectionState.inprogress;
321✔
77
  const { page: pendingPage, perPage: pendingPerPage } = selectionState.pending;
321✔
78

79
  const [doneLoading, setDoneLoading] = useState(!!(progressCount || pendingCount));
321✔
80
  // eslint-disable-next-line no-unused-vars
81
  const size = useWindowSize();
321✔
82

83
  const currentRefreshDeploymentLength = useRef(refreshDeploymentsLength);
321✔
84
  const inprogressRef = useRef();
321✔
85
  const dynamicTimer = useRef();
321✔
86

87
  const { classes } = useStyles();
321✔
88

89
  // deploymentStatus = <inprogress|pending>
90
  const refreshDeployments = useCallback(
321✔
91
    deploymentStatus => {
92
      const { page, perPage } = selectionState[deploymentStatus];
58✔
93
      return dispatch(getDeploymentsByStatus(deploymentStatus, page, perPage))
58✔
94
        .then(deploymentsAction => {
95
          clearRetryTimer(deploymentStatus, dispatchedSetSnackbar);
54✔
96
          const { total, deploymentIds } = deploymentsAction[deploymentsAction.length - 1];
54✔
97
          if (total && !deploymentIds.length) {
54!
98
            return refreshDeployments(deploymentStatus);
×
99
          }
100
        })
101
        .catch(err => setRetryTimer(err, 'deployments', `Couldn't load deployments.`, refreshDeploymentsLength, dispatchedSetSnackbar))
×
102
        .finally(() => setDoneLoading(true));
54✔
103
    },
104
    // eslint-disable-next-line react-hooks/exhaustive-deps
105
    [dispatch, dispatchedSetSnackbar, pendingPage, pendingPerPage, progressPage, progressPerPage]
106
  );
107

108
  const setupDeploymentsRefresh = useCallback(
321✔
109
    (refreshLength = currentRefreshDeploymentLength.current) => {
11✔
110
      let tasks = [refreshDeployments(DEPLOYMENT_STATES.inprogress), refreshDeployments(DEPLOYMENT_STATES.pending)];
29✔
111
      if (!onboardingState.complete && !pastDeploymentsCount) {
29✔
112
        // retrieve past deployments outside of the regular refresh cycle to not change the selection state for past deployments
113
        dispatch(getDeploymentsByStatus(DEPLOYMENT_STATES.finished, 1, 1, undefined, undefined, undefined, undefined, false));
10✔
114
      }
115
      return Promise.all(tasks)
29✔
116
        .then(() => {
117
          currentRefreshDeploymentLength.current = Math.min(refreshDeploymentsLength, refreshLength * 2);
27✔
118
          clearTimeout(dynamicTimer.current);
27✔
119
          dynamicTimer.current = setTimeout(setupDeploymentsRefresh, currentRefreshDeploymentLength.current);
27✔
120
        })
121
        .finally(() => setDoneLoading(true));
27✔
122
    },
123
    [dispatch, onboardingState.complete, pastDeploymentsCount, refreshDeployments]
124
  );
125

126
  useEffect(() => {
321✔
127
    return () => {
8✔
128
      clearTimeout(dynamicTimer.current);
8✔
129
    };
130
  }, []);
131

132
  useEffect(() => {
321✔
133
    return () => {
8✔
134
      clearAllRetryTimers(dispatchedSetSnackbar);
8✔
135
    };
136
  }, [dispatchedSetSnackbar]);
137

138
  useEffect(() => {
321✔
139
    clearTimeout(dynamicTimer.current);
18✔
140
    setupDeploymentsRefresh(minimalRefreshDeploymentsLength);
18✔
141
    return () => {
18✔
142
      clearTimeout(dynamicTimer.current);
18✔
143
    };
144
  }, [pendingCount, setupDeploymentsRefresh]);
145

146
  useEffect(() => {
321✔
147
    clearTimeout(dynamicTimer.current);
11✔
148
    setupDeploymentsRefresh();
11✔
149
    return () => {
11✔
150
      clearTimeout(dynamicTimer.current);
11✔
151
    };
152
  }, [progressPage, progressPerPage, pendingPage, pendingPerPage, setupDeploymentsRefresh]);
153

154
  const abortDeployment = id =>
321✔
155
    abort(id).then(() => Promise.all([refreshDeployments(DEPLOYMENT_STATES.inprogress), refreshDeployments(DEPLOYMENT_STATES.pending)]));
×
156

157
  const onChangePage = state => page => dispatch(setDeploymentsState({ [state]: { page } }));
611✔
158
  const onChangeRowsPerPage = state => perPage => dispatch(setDeploymentsState({ [state]: { page: 1, perPage } }));
611✔
159

160
  let onboardingComponent = null;
321✔
161
  if (!onboardingState.complete && inprogressRef.current) {
321✔
162
    const anchor = {
194✔
163
      left: inprogressRef.current.offsetLeft + (inprogressRef.current.offsetWidth / 100) * 90,
164
      top: inprogressRef.current.offsetTop + inprogressRef.current.offsetHeight
165
    };
166
    onboardingComponent = getOnboardingComponentFor(onboardingSteps.DEPLOYMENTS_INPROGRESS, onboardingState, { anchor });
194✔
167
  }
168
  const props = { ...remainder, canDeploy, canConfigure, devices, idAttribute, isEnterprise };
321✔
169
  return doneLoading ? (
321✔
170
    <div className="fadeIn">
171
      {!!progress.length && (
614✔
172
        <div className="margin-left">
173
          <LinedHeader className="margin-top-large  margin-right" heading="In progress now" />
174
          <DeploymentsList
175
            {...props}
176
            abort={abortDeployment}
177
            count={progressCount}
178
            items={progress}
179
            listClass="margin-right-small"
180
            page={progressPage}
181
            pageSize={progressPerPage}
182
            rootRef={inprogressRef}
183
            onChangeRowsPerPage={onChangeRowsPerPage(DEPLOYMENT_STATES.inprogress)}
184
            onChangePage={onChangePage(DEPLOYMENT_STATES.inprogress)}
185
            type={DEPLOYMENT_STATES.inprogress}
186
          />
187
        </div>
188
      )}
189
      {!!onboardingComponent && onboardingComponent}
307!
190
      {!!pending.length && (
611✔
191
        <div className={`deployments-pending margin-top margin-bottom-large ${classes.deploymentsPending}`}>
192
          <LinedHeader className="margin-small margin-top" heading="Pending" />
193
          <DeploymentsList
194
            {...props}
195
            abort={abortDeployment}
196
            componentClass="margin-left-small"
197
            count={pendingCount}
198
            items={pending}
199
            page={pendingPage}
200
            pageSize={pendingPerPage}
201
            onChangeRowsPerPage={onChangeRowsPerPage(DEPLOYMENT_STATES)}
202
            onChangePage={onChangePage(DEPLOYMENT_STATES.pending)}
203
            type={DEPLOYMENT_STATES.pending}
204
          />
205
        </div>
206
      )}
207
      {!(progressCount || pendingCount) && (
614!
208
        <div className="dashboard-placeholder">
209
          <p>Pending and ongoing deployments will appear here. </p>
210
          {canDeploy && (
×
211
            <p>
212
              <a onClick={createClick}>Create a deployment</a> to get started
213
            </p>
214
          )}
215
          <RefreshIcon className="flip-horizontal" style={{ fill: '#e3e3e3', width: 111, height: 111 }} />
216
        </div>
217
      )}
218
    </div>
219
  ) : (
220
    <Loader show={doneLoading} />
221
  );
222
};
223

224
export default Progress;
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