• 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

96.05
/frontend/src/js/components/dashboard/Deployments.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 React, { useCallback, useEffect, useRef, useState } from 'react';
2✔
15
import { useDispatch, useSelector } from 'react-redux';
2✔
16
import { Link } from 'react-router-dom';
2✔
17

2✔
18
import Loader from '@northern.tech/common-ui/Loader';
2✔
19
import storeActions from '@northern.tech/store/actions';
2✔
20
import { DEPLOYMENT_ROUTES, DEPLOYMENT_STATES, TIMEOUTS, deploymentDisplayStates, onboardingSteps } from '@northern.tech/store/constants';
2✔
21
import {
2✔
22
  DEPLOYMENT_CUTOFF,
2✔
23
  getDevicesById,
2✔
24
  getIdAttribute,
2✔
25
  getOnboardingState,
2✔
26
  getRecentDeployments,
2✔
27
  getUserCapabilities
2✔
28
} from '@northern.tech/store/selectors';
2✔
29
import { getDeploymentsByStatus } from '@northern.tech/store/thunks';
2✔
30
import { useWindowSize } from '@northern.tech/utils/resizehook';
2✔
31
import { clearAllRetryTimers, setRetryTimer } from '@northern.tech/utils/retrytimer';
2✔
32

2✔
33
import { getOnboardingComponentFor } from '../../utils/onboardingManager';
2✔
34
import { BaseDeploymentsWidget, CompletedDeployments } from './widgets/Deployments';
2✔
35
import RedirectionWidget from './widgets/RedirectionWidget';
2✔
36

2✔
37
const { setSnackbar } = storeActions;
7✔
38

2✔
39
const refreshDeploymentsLength = 30000;
7✔
40

2✔
41
// we need to exclude the scheduled state here as the os version is not able to process these and would prevent the dashboard from loading
2✔
42
const stateMap = {
7✔
43
  [DEPLOYMENT_STATES.pending]: BaseDeploymentsWidget,
2✔
44
  [DEPLOYMENT_STATES.inprogress]: BaseDeploymentsWidget,
2✔
45
  [DEPLOYMENT_STATES.finished]: CompletedDeployments
2✔
46
};
2✔
47

2✔
48
export const Deployments = ({ className = '', clickHandle }) => {
7✔
49
  const dispatch = useDispatch();
71✔
50
  const setSnackbarDispatched = useCallback(message => dispatch(setSnackbar(message)), [dispatch]);
71✔
51
  const { canDeploy } = useSelector(getUserCapabilities);
71✔
52
  const { total: deploymentsCount, ...deployments } = useSelector(getRecentDeployments);
71✔
53
  const onboardingState = useSelector(getOnboardingState);
71✔
54
  const devicesById = useSelector(getDevicesById);
71✔
55
  const idAttribute = useSelector(getIdAttribute);
71✔
56
  const [loading, setLoading] = useState(!deploymentsCount);
71✔
57
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
2✔
58
  const size = useWindowSize();
71✔
59
  const deploymentsRef = useRef<HTMLDivElement | null>(null);
71✔
60
  const timer = useRef<ReturnType<typeof setTimeout> | undefined>();
71✔
61
  const timeoutTimer = useRef<ReturnType<typeof setTimeout> | undefined>();
71✔
62

2✔
63
  const getDeployments = useCallback(
71✔
64
    () =>
2✔
65
      Promise.all(Object.keys(stateMap).map(status => dispatch(getDeploymentsByStatus({ status, page: 1, perPage: DEPLOYMENT_CUTOFF }))))
134✔
UNCOV
66
        .catch(err => setRetryTimer(err, 'deployments', `Couldn't load deployments.`, refreshDeploymentsLength, setSnackbarDispatched))
2✔
67
        .finally(() => setLoading(false)),
46✔
68
    [dispatch, setSnackbarDispatched]
2✔
69
  );
2✔
70

2✔
71
  useEffect(() => {
71✔
72
    clearAllRetryTimers(setSnackbarDispatched);
12✔
73
    clearInterval(timer.current);
12✔
74
    timer.current = setInterval(getDeployments, refreshDeploymentsLength);
12✔
75
    getDeployments();
12✔
76
    return () => {
12✔
77
      clearInterval(timer.current);
12✔
78
      clearAllRetryTimers(setSnackbarDispatched);
12✔
79
    };
2✔
80
  }, [getDeployments, setSnackbarDispatched]);
2✔
81

2✔
82
  useEffect(() => {
71✔
83
    timeoutTimer.current = setTimeout(() => setLoading(false), TIMEOUTS.debounceDefault);
12✔
84
    return () => clearTimeout(timeoutTimer.current);
12✔
85
  }, []);
2✔
86

2✔
87
  let onboardingComponent;
2✔
88
  if (deploymentsRef.current) {
71✔
89
    const anchor = {
55✔
90
      top: deploymentsRef.current.offsetTop + deploymentsRef.current.offsetHeight,
2✔
91
      left: deploymentsRef.current.offsetLeft + deploymentsRef.current.offsetWidth / 2
2✔
92
    };
2✔
93
    onboardingComponent = getOnboardingComponentFor(onboardingSteps.DEPLOYMENTS_PAST_COMPLETED, onboardingState, { anchor });
55✔
94
  }
2✔
95
  return (
71✔
96
    <div className={`${className} deployments`}>
2✔
97
      {loading ? (
2✔
98
        <Loader show={loading} fade={true} />
2✔
99
      ) : (
2✔
100
        <div className="dashboard flexbox column" ref={deploymentsRef} style={{ gridTemplateColumns: '1fr', rowGap: 10 }}>
2✔
101
          <h4 className={`${deploymentsCount ? 'margin-bottom-none' : 'margin-top-none'} margin-left-small`}>
2!
102
            {deploymentsCount ? 'Recent deployments' : 'Deployments'}
2!
103
          </h4>
2✔
104
          {deploymentsCount ? (
2!
105
            <>
2✔
106
              {Object.entries(stateMap).reduce((accu, [key, Component]) => {
2✔
107
                if (!deployments[key]) {
191!
108
                  return accu;
2✔
109
                }
2✔
110
                accu.push(
191✔
111
                  <React.Fragment key={key}>
2✔
112
                    <h5 className="margin-bottom-none">{deploymentDisplayStates[key]}</h5>
2✔
113
                    <Component deployments={deployments[key]} devicesById={devicesById} idAttribute={idAttribute} state={key} onClick={clickHandle} />
2✔
114
                  </React.Fragment>
2✔
115
                );
2✔
116
                return accu;
191✔
117
              }, [])}
2✔
118
              <Link className="margin-top margin-bottom-large" to="/deployments">
2✔
119
                See all deployments
2✔
120
              </Link>
2✔
121
            </>
2✔
122
          ) : (
2✔
123
            canDeploy && (
2!
124
              <RedirectionWidget
2✔
125
                content="Create a new deployment to update a group of devices"
2✔
126
                onClick={() => clickHandle({ route: `${DEPLOYMENT_ROUTES.active.route}?open=true` })}
2✔
127
              />
2✔
128
            )
2✔
129
          )}
2✔
130
        </div>
2✔
131
      )}
2✔
132
      {onboardingComponent}
2✔
133
    </div>
2✔
134
  );
2✔
135
};
2✔
136

2✔
137
export default Deployments;
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