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

mendersoftware / mender-server / 10423

11 Nov 2025 04:53PM UTC coverage: 74.435% (-0.1%) from 74.562%
10423

push

gitlab-ci

web-flow
Merge pull request #1071 from mendersoftware/dependabot/npm_and_yarn/frontend/main/development-dependencies-92732187be

3868 of 5393 branches covered (71.72%)

Branch coverage included in aggregate %.

5 of 5 new or added lines in 2 files covered. (100.0%)

176 existing lines in 95 files now uncovered.

64605 of 86597 relevant lines covered (74.6%)

7.74 hits per line

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

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

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

2✔
20
import LinedHeader from '@northern.tech/common-ui/LinedHeader';
2✔
21
import Loader from '@northern.tech/common-ui/Loader';
2✔
22
import storeActions from '@northern.tech/store/actions';
2✔
23
import { DEPLOYMENT_STATES, onboardingSteps } from '@northern.tech/store/constants';
2✔
24
import {
2✔
25
  getDeploymentsByStatus as getDeploymentsByStatusSelector,
2✔
26
  getDeploymentsSelectionState,
2✔
27
  getDevicesById,
2✔
28
  getIdAttribute,
2✔
29
  getIsEnterprise,
2✔
30
  getMappedDeploymentSelection,
2✔
31
  getOnboardingState,
2✔
32
  getUserCapabilities
2✔
33
} from '@northern.tech/store/selectors';
2✔
34
import { getDeploymentsByStatus, setDeploymentsState } from '@northern.tech/store/thunks';
2✔
35
import { useWindowSize } from '@northern.tech/utils/resizehook';
2✔
36
import { clearAllRetryTimers, clearRetryTimer, setRetryTimer } from '@northern.tech/utils/retrytimer';
2✔
37

2✔
38
import { getOnboardingComponentFor } from '../../utils/onboardingManager';
2✔
39
import DeploymentsList from './DeploymentsList';
2✔
40
import { defaultRefreshDeploymentsLength as refreshDeploymentsLength } from './constants';
2✔
41

2✔
42
const { setSnackbar } = storeActions;
7✔
43

2✔
44
export const minimalRefreshDeploymentsLength = 2000;
7✔
45

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

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

2✔
78
  const { page: progressPage, perPage: progressPerPage } = selectionState.inprogress;
130✔
79
  const { page: pendingPage, perPage: pendingPerPage } = selectionState.pending;
130✔
80

2✔
81
  const [doneLoading, setDoneLoading] = useState(!!(progressCount || pendingCount));
130✔
82
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
2✔
83
  const size = useWindowSize();
130✔
84

2✔
85
  const currentRefreshDeploymentLength = useRef(refreshDeploymentsLength);
130✔
86
  const inprogressRef = useRef();
130✔
87
  const dynamicTimer = useRef();
130✔
88

2✔
89
  const { classes } = useStyles();
130✔
90

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

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

2✔
128
  useEffect(
130✔
129
    () => () => {
9✔
130
      clearTimeout(dynamicTimer.current);
9✔
131
    },
2✔
132
    []
2✔
133
  );
2✔
134

2✔
135
  useEffect(
130✔
136
    () => () => {
9✔
137
      clearAllRetryTimers(dispatchedSetSnackbar);
9✔
138
    },
2✔
139
    [dispatchedSetSnackbar]
2✔
140
  );
2✔
141

2✔
142
  useEffect(() => {
130✔
143
    clearTimeout(dynamicTimer.current);
14✔
144
    setupDeploymentsRefresh(minimalRefreshDeploymentsLength);
14✔
145
    return () => {
14✔
146
      clearTimeout(dynamicTimer.current);
14✔
147
    };
2✔
148
  }, [pendingCount, setupDeploymentsRefresh]);
2✔
149

2✔
150
  useEffect(() => {
130✔
151
    clearTimeout(dynamicTimer.current);
11✔
152
    setupDeploymentsRefresh();
11✔
153
    return () => {
11✔
154
      clearTimeout(dynamicTimer.current);
11✔
155
    };
2✔
156
  }, [progressPage, progressPerPage, pendingPage, pendingPerPage, setupDeploymentsRefresh]);
2✔
157

2✔
158
  const abortDeployment = id =>
130✔
159
    abort(id).then(() => Promise.all([refreshDeployments(DEPLOYMENT_STATES.inprogress), refreshDeployments(DEPLOYMENT_STATES.pending)]));
2✔
160

2✔
161
  const onChangePage = state => page => dispatch(setDeploymentsState({ [state]: { page } }));
214✔
162
  const onChangeRowsPerPage = state => perPage => dispatch(setDeploymentsState({ [state]: { page: 1, perPage } }));
214✔
163

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

2✔
228
export default Progress;
2✔
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