• 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

77.68
/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
import React, { useEffect, useRef, useState } from 'react';
15
import { connect } from 'react-redux';
16
import { Link, useNavigate } from 'react-router-dom';
17

18
import { Button, Tab, Tabs } from '@mui/material';
19

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

38
const routes = {
7✔
39
  [DEPLOYMENT_ROUTES.active.key]: {
40
    ...DEPLOYMENT_ROUTES.active,
41
    component: Progress
42
  },
43
  [DEPLOYMENT_ROUTES.scheduled.key]: {
44
    ...DEPLOYMENT_ROUTES.scheduled,
45
    component: Scheduled
46
  },
47
  [DEPLOYMENT_ROUTES.finished.key]: {
48
    ...DEPLOYMENT_ROUTES.finished,
49
    component: Past
50
  }
51
};
52

53
export const defaultRefreshDeploymentsLength = 30000;
7✔
54

55
export const Deployments = ({
7✔
56
  abortDeployment,
57
  advanceOnboarding,
58
  devicesById,
59
  getDynamicGroups,
60
  getGroups,
61
  groupsById,
62
  isEnterprise,
63
  onboardingState,
64
  pastCount,
65
  releases,
66
  selectionState,
67
  setDeploymentsState,
68
  setSnackbar,
69
  userCapabilities
70
}) => {
71
  const [deploymentObject, setDeploymentObject] = useState({});
273✔
72
  // eslint-disable-next-line no-unused-vars
73
  const size = useWindowSize();
273✔
74
  const tabsRef = useRef();
273✔
75
  const isInitialized = useRef(false);
273✔
76
  const navigate = useNavigate();
273✔
77
  const { reportType, showCreationDialog: createDialog, showReportDialog: reportDialog, state } = selectionState.general;
273✔
78
  const { canDeploy, canReadReleases } = userCapabilities;
273✔
79

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

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

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

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

122
  const retryDeployment = (deployment, deploymentDeviceIds) => {
273✔
123
    const { artifact_name, name, update_control_map = {} } = deployment;
×
124
    const release = releases[artifact_name];
×
125
    const enterpriseSettings = isEnterprise
×
126
      ? {
127
          phases: [{ batch_size: 100, start_ts: undefined, delay: 0 }],
128
          update_control_map: { states: update_control_map.states || {} }
×
129
        }
130
      : {};
131
    const targetDevicesConfig = name === ALL_DEVICES || groupsById[name] ? { group: name } : { devices: [devicesById[name]] };
×
132
    const deploymentObject = {
×
133
      deploymentDeviceIds,
134
      release,
135
      deploymentDeviceCount: deploymentDeviceIds.length,
136
      ...targetDevicesConfig,
137
      ...enterpriseSettings
138
    };
139
    setDeploymentObject(deploymentObject);
×
140
    setDeploymentsState({ general: { showCreationDialog: true, showReportDialog: false } });
×
141
  };
142

143
  const onScheduleSubmit = () => {
273✔
144
    setDeploymentsState({ general: { showCreationDialog: false, showReportDialog: false } });
2✔
145
    setDeploymentObject({});
2✔
146
    // successfully retrieved new deployment
147
    if (routes.active.key !== state) {
2✔
148
      navigate(routes.active.route);
1✔
149
      changeTab(undefined, routes.active.key);
1✔
150
    }
151
  };
152

153
  const onAbortDeployment = id =>
273✔
154
    abortDeployment(id).then(() => {
×
155
      setDeploymentsState({ general: { showCreationDialog: false, showReportDialog: false } });
×
156
      return Promise.resolve();
×
157
    });
158

159
  const changeTab = (_, tabIndex) => {
273✔
160
    setDeploymentsState({ general: { state: tabIndex } });
5✔
161
    setSnackbar('');
5✔
162
    if (pastCount && !onboardingState.complete) {
5✔
163
      advanceOnboarding(onboardingSteps.DEPLOYMENTS_PAST);
1✔
164
    }
165
  };
166

167
  const showReport = (reportType, selectedId) => {
273✔
168
    if (!onboardingState.complete) {
1!
169
      advanceOnboarding(onboardingSteps.DEPLOYMENTS_INPROGRESS);
1✔
170
    }
171
    setDeploymentsState({ general: { reportType, showCreationDialog: false, showReportDialog: true }, selectedId });
1✔
172
  };
173

174
  const closeReport = () => setDeploymentsState({ general: { reportType: undefined, showReportDialog: false }, selectedId: undefined });
273✔
175

176
  const onCreationDismiss = () => {
273✔
177
    setDeploymentsState({ general: { showCreationDialog: false } });
1✔
178
    setDeploymentObject({});
1✔
179
  };
180

181
  const onCreationShow = () => setDeploymentsState({ general: { showCreationDialog: true } });
273✔
182

183
  let onboardingComponent = null;
273✔
184
  // the pastCount prop is needed to trigger the rerender as the change in past deployments would otherwise not be noticed on this view
185
  if (pastCount && tabsRef.current && !reportDialog) {
273✔
186
    const tabs = tabsRef.current.getElementsByClassName('MuiTab-root');
213✔
187
    const finishedTab = tabs[tabs.length - 1];
213✔
188
    onboardingComponent = getOnboardingComponentFor(onboardingSteps.DEPLOYMENTS_PAST, onboardingState, {
213✔
189
      anchor: {
190
        left: tabsRef.current.offsetLeft + tabsRef.current.offsetWidth - finishedTab.offsetWidth / 2,
191
        top: tabsRef.current.parentElement.offsetTop + finishedTab.offsetHeight
192
      }
193
    });
194
  }
195

196
  const ComponentToShow = routes[state].component;
273✔
197
  return (
273✔
198
    <>
199
      <div className="margin-left-small margin-top" style={{ maxWidth: '80vw' }}>
200
        <div className="flexbox space-between">
201
          <Tabs value={state} onChange={changeTab} ref={tabsRef}>
202
            {Object.values(routes).map(route => (
203
              <Tab component={Link} key={route.route} label={route.title} to={route.route} value={route.key} />
819✔
204
            ))}
205
          </Tabs>
206
          {canDeploy && canReadReleases && (
819✔
207
            <Button color="secondary" variant="contained" onClick={onCreationShow} style={{ height: '100%' }}>
208
              Create a deployment
209
            </Button>
210
          )}
211
        </div>
212
        <ComponentToShow abort={onAbortDeployment} createClick={onCreationShow} openReport={showReport} isShowingDetails={reportDialog} />
213
      </div>
214
      <Report abort={onAbortDeployment} onClose={closeReport} open={reportDialog} retry={retryDeployment} type={reportType} />
215
      <CreateDeployment
216
        open={createDialog}
217
        onDismiss={onCreationDismiss}
218
        deploymentObject={deploymentObject}
219
        onScheduleSubmit={onScheduleSubmit}
220
        setDeploymentObject={setDeploymentObject}
221
      />
222
      {!reportDialog && onboardingComponent}
544✔
223
    </>
224
  );
225
};
226

227
const actionCreators = {
7✔
228
  abortDeployment,
229
  advanceOnboarding,
230
  getGroups,
231
  getDynamicGroups,
232
  setDeploymentsState,
233
  setSnackbar
234
};
235

236
const mapStateToProps = state => {
7✔
237
  // eslint-disable-next-line no-unused-vars
238
  const { [UNGROUPED_GROUP.id]: ungrouped, ...groups } = state.devices.groups.byId;
215✔
239
  return {
215✔
240
    devicesById: state.devices.byId,
241
    groupsById: groups,
242
    isEnterprise: getIsEnterprise(state),
243
    onboardingState: getOnboardingState(state),
244
    pastCount: state.deployments.byStatus.finished.total,
245
    releases: state.releases.byId,
246
    selectionState: state.deployments.selectionState,
247
    settings: state.users.globalSettings,
248
    userCapabilities: getUserCapabilities(state)
249
  };
250
};
251

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