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

mendersoftware / gui / 1350829378

27 Jun 2024 01:46PM UTC coverage: 83.494% (-16.5%) from 99.965%
1350829378

Pull #4465

gitlab-ci

mzedel
chore: test fixes

Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #4465: MEN-7169 - feat: added multi sorting capabilities to devices view

4506 of 6430 branches covered (70.08%)

81 of 100 new or added lines in 14 files covered. (81.0%)

1661 existing lines in 163 files now uncovered.

8574 of 10269 relevant lines covered (83.49%)

160.6 hits per line

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

86.75
/src/js/components/deployments/deployments.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

15
/* eslint-disable react-hooks/exhaustive-deps */
16
import React, { useCallback, useEffect, useRef, useState } from 'react';
17
import { useDispatch, useSelector } from 'react-redux';
18
import { Link, useNavigate } from 'react-router-dom';
19

20
import { Button, Tab, Tabs } from '@mui/material';
21

22
import { isUUID } from 'validator';
23

24
import { setSnackbar } from '../../actions/appActions';
25
import { abortDeployment, setDeploymentsState } from '../../actions/deploymentActions';
26
import { getDynamicGroups, getGroups } from '../../actions/deviceActions';
27
import { advanceOnboarding } from '../../actions/onboardingActions';
28
import { DEPLOYMENT_ROUTES, DEPLOYMENT_STATES, listDefaultsByState } from '../../constants/deploymentConstants';
29
import { ALL_DEVICES } from '../../constants/deviceConstants';
30
import { onboardingSteps } from '../../constants/onboardingConstants';
31
import { getISOStringBoundaries } from '../../helpers';
32
import { getDevicesById, getGroupsByIdWithoutUngrouped, getIsEnterprise, getOnboardingState, getReleasesById, getUserCapabilities } from '../../selectors';
33
import { useLocationParams } from '../../utils/liststatehook';
34
import { getOnboardingComponentFor } from '../../utils/onboardingmanager';
35
import useWindowSize from '../../utils/resizehook';
36
import CreateDeployment from './createdeployment';
37
import Progress from './inprogressdeployments';
38
import Past from './pastdeployments';
39
import Report from './report';
40
import Scheduled from './scheduleddeployments';
41

42
const routes = {
6✔
43
  [DEPLOYMENT_ROUTES.active.key]: {
44
    ...DEPLOYMENT_ROUTES.active,
45
    component: Progress
46
  },
47
  [DEPLOYMENT_ROUTES.scheduled.key]: {
48
    ...DEPLOYMENT_ROUTES.scheduled,
49
    component: Scheduled
50
  },
51
  [DEPLOYMENT_ROUTES.finished.key]: {
52
    ...DEPLOYMENT_ROUTES.finished,
53
    component: Past
54
  }
55
};
56

57
export const defaultRefreshDeploymentsLength = 30000;
6✔
58

59
export const Deployments = () => {
6✔
60
  const groupsById = useSelector(getGroupsByIdWithoutUngrouped);
116✔
61
  const devicesById = useSelector(getDevicesById);
115✔
62
  const isEnterprise = useSelector(getIsEnterprise);
115✔
63
  const onboardingState = useSelector(getOnboardingState);
115✔
64
  const pastCount = useSelector(state => state.deployments.byStatus.finished.total);
377✔
65
  const releases = useSelector(getReleasesById);
115✔
66
  const selectionState = useSelector(state => state.deployments.selectionState);
377✔
67
  const userCapabilities = useSelector(getUserCapabilities);
115✔
68
  const dispatch = useDispatch();
115✔
69

70
  const [deploymentObject, setDeploymentObject] = useState({});
115✔
71
  // eslint-disable-next-line no-unused-vars
72
  const size = useWindowSize();
115✔
73
  const tabsRef = useRef();
115✔
74
  const isInitialized = useRef(false);
115✔
75
  const deploymentObjInitialized = useRef(false);
115✔
76
  const navigate = useNavigate();
115✔
77
  const { reportType, showCreationDialog: createDialog, showReportDialog: reportDialog, state } = selectionState.general;
115✔
78
  const { canDeploy, canReadReleases } = userCapabilities;
115✔
79

80
  const [date] = useState(getISOStringBoundaries(new Date()));
115✔
81
  const { start: today, end: tonight } = date;
115✔
82

83
  const [locationParams, setLocationParams] = useLocationParams('deployments', { today, tonight, defaults: listDefaultsByState });
115✔
84

85
  useEffect(() => {
115✔
86
    if (!isInitialized.current) {
49✔
87
      return;
4✔
88
    }
89
    setLocationParams({ deploymentObject, pageState: selectionState });
45✔
90
  }, [
91
    JSON.stringify(deploymentObject),
92
    selectionState.selectedId,
93
    selectionState.general.state,
94
    selectionState.general.showCreationDialog,
95
    selectionState.general.showReportDialog,
96
    selectionState.general.reportType,
97
    selectionState[DEPLOYMENT_STATES.finished].endDate,
98
    selectionState[DEPLOYMENT_STATES.finished].search,
99
    selectionState[DEPLOYMENT_STATES.finished].startDate,
100
    selectionState[DEPLOYMENT_STATES.finished].page,
101
    selectionState[DEPLOYMENT_STATES.finished].perPage,
102
    selectionState[DEPLOYMENT_STATES.finished].type,
103
    selectionState[DEPLOYMENT_STATES.inprogress].page,
104
    selectionState[DEPLOYMENT_STATES.inprogress].perPage,
105
    selectionState[DEPLOYMENT_STATES.pending].page,
106
    selectionState[DEPLOYMENT_STATES.pending].perPage,
107
    setLocationParams
108
  ]);
109

110
  useEffect(() => {
115✔
111
    dispatch(getGroups());
4✔
112
    if (isEnterprise) {
4✔
113
      dispatch(getDynamicGroups());
3✔
114
    }
115
    const { deploymentObject = {}, id: selectedId = [], ...remainder } = locationParams;
4!
116
    const { devices: selectedDevices = [], release: releaseName } = deploymentObject;
4✔
117
    const release = releaseName ? { ...(releases[releaseName] ?? { name: releaseName }) } : undefined;
4!
118
    const devices = selectedDevices.length ? selectedDevices.map(device => ({ ...device, ...devicesById[device.id] })) : [];
4!
119
    setDeploymentObject({ devices, release, releaseSelectionLocked: !!release });
4✔
120
    dispatch(setDeploymentsState({ selectedId: selectedId[0], ...remainder }));
4✔
121
    isInitialized.current = true;
4✔
122
  }, [dispatch, isEnterprise]);
123

124
  useEffect(() => {
115✔
125
    if (Object.keys(deploymentObject).length > 0) {
50✔
126
      // render create deployment dialog when the deployment object is initialized
127
      // otherwise CreateDeployment.setDeploymentSettings will remove device id
128
      deploymentObjInitialized.current = true;
43✔
129
    }
130
  }, [deploymentObject]);
131

132
  const retryDeployment = (deployment, deploymentDeviceIds) => {
115✔
UNCOV
133
    const { artifact_name, name, update_control_map = {} } = deployment;
×
UNCOV
134
    const release = releases[artifact_name] || { name: artifact_name };
×
UNCOV
135
    const enterpriseSettings = isEnterprise
×
136
      ? {
137
          phases: [{ batch_size: 100, start_ts: undefined, delay: 0 }],
138
          update_control_map: { states: update_control_map.states || {} }
×
139
        }
140
      : {};
141
    const targetDevicesConfig =
UNCOV
142
      name === ALL_DEVICES || groupsById[name]
×
143
        ? { group: name }
UNCOV
144
        : { devices: isUUID(name) ? [devicesById[name]] : deploymentDeviceIds.map(id => devicesById[id] ?? { id }) };
×
UNCOV
145
    const deploymentObject = {
×
146
      deploymentDeviceIds,
147
      release,
148
      deploymentDeviceCount: deploymentDeviceIds.length,
149
      ...targetDevicesConfig,
150
      ...enterpriseSettings
151
    };
UNCOV
152
    setDeploymentObject(deploymentObject);
×
UNCOV
153
    dispatch(setDeploymentsState({ general: { showCreationDialog: true, showReportDialog: false } }));
×
154
  };
155

156
  const onScheduleSubmit = () => {
115✔
157
    dispatch(setDeploymentsState({ general: { showCreationDialog: false, showReportDialog: false } }));
2✔
158
    setDeploymentObject({});
2✔
159
    // successfully retrieved new deployment
160
    if (routes.active.key !== state) {
2✔
161
      navigate(routes.active.route);
1✔
162
      changeTab(undefined, routes.active.key);
1✔
163
    }
164
  };
165

166
  const onAbortDeployment = id =>
115✔
UNCOV
167
    dispatch(abortDeployment(id)).then(() => {
×
UNCOV
168
      dispatch(setDeploymentsState({ general: { showCreationDialog: false, showReportDialog: false } }));
×
UNCOV
169
      return Promise.resolve();
×
170
    });
171

172
  const changeTab = (_, tabIndex) => {
115✔
173
    dispatch(setDeploymentsState({ general: { state: tabIndex } }));
5✔
174
    dispatch(setSnackbar(''));
5✔
175
    if (pastCount && !onboardingState.complete) {
5✔
176
      dispatch(advanceOnboarding(onboardingSteps.DEPLOYMENTS_PAST));
2✔
177
    }
178
  };
179

180
  const showReport = (reportType, selectedId) => {
115✔
181
    if (!onboardingState.complete) {
1!
182
      dispatch(advanceOnboarding(onboardingSteps.DEPLOYMENTS_INPROGRESS));
1✔
183
    }
184
    dispatch(setDeploymentsState({ general: { reportType, showCreationDialog: false, showReportDialog: true }, selectedId }));
1✔
185
  };
186

187
  const closeReport = () => dispatch(setDeploymentsState({ general: { reportType: undefined, showReportDialog: false }, selectedId: undefined }));
115✔
188

189
  const onCreationDismiss = () => {
115✔
190
    dispatch(setDeploymentsState({ general: { showCreationDialog: false } }));
1✔
191
    setDeploymentObject({});
1✔
192
  };
193

194
  const onCreationShow = () => dispatch(setDeploymentsState({ general: { showCreationDialog: true } }));
115✔
195

196
  const setDeploymentSettings = useCallback(change => setDeploymentObject(current => ({ ...current, ...change })), []);
115✔
197

198
  let onboardingComponent = null;
115✔
199
  // the pastCount prop is needed to trigger the rerender as the change in past deployments would otherwise not be noticed on this view
200
  if (pastCount && tabsRef.current && !reportDialog) {
115✔
201
    const tabs = tabsRef.current.getElementsByClassName('MuiTab-root');
78✔
202
    const finishedTab = tabs[tabs.length - 1];
78✔
203
    onboardingComponent = getOnboardingComponentFor(onboardingSteps.DEPLOYMENTS_PAST, onboardingState, {
78✔
204
      anchor: {
205
        left: tabsRef.current.offsetLeft + tabsRef.current.offsetWidth - finishedTab.offsetWidth / 2,
206
        top: tabsRef.current.parentElement.offsetTop + finishedTab.offsetHeight
207
      }
208
    });
209
  }
210

211
  const ComponentToShow = routes[state].component;
115✔
212
  return (
115✔
213
    <>
214
      <div className="margin-left-small margin-top" style={{ maxWidth: '80vw' }}>
215
        <div className="flexbox space-between">
216
          <Tabs value={state} onChange={changeTab} ref={tabsRef}>
217
            {Object.values(routes).map(route => (
218
              <Tab component={Link} key={route.route} label={route.title} to={route.route} value={route.key} />
345✔
219
            ))}
220
          </Tabs>
221
          {canDeploy && canReadReleases && (
345✔
222
            <Button color="secondary" variant="contained" onClick={onCreationShow} style={{ height: '100%' }}>
223
              Create a deployment
224
            </Button>
225
          )}
226
        </div>
227
        <ComponentToShow abort={onAbortDeployment} createClick={onCreationShow} openReport={showReport} isShowingDetails={reportDialog} />
228
      </div>
229
      {reportDialog && <Report abort={onAbortDeployment} onClose={closeReport} retry={retryDeployment} type={reportType} />}
117✔
230
      {createDialog && deploymentObjInitialized.current && (
231✔
231
        <CreateDeployment
232
          onDismiss={onCreationDismiss}
233
          deploymentObject={deploymentObject}
234
          onScheduleSubmit={onScheduleSubmit}
235
          setDeploymentSettings={setDeploymentSettings}
236
        />
237
      )}
238
      {!reportDialog && onboardingComponent}
228✔
239
    </>
240
  );
241
};
242

243
export default Deployments;
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