• 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

58.76
/src/js/components/deployments/scheduleddeployments.js
1
// Copyright 2020 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 { Calendar, momentLocalizer } from 'react-big-calendar';
16
import 'react-big-calendar/lib/css/react-big-calendar.css';
17
import { connect } from 'react-redux';
18

19
import { CalendarToday as CalendarTodayIcon, List as ListIcon, Refresh as RefreshIcon } from '@mui/icons-material';
20
import { Button } from '@mui/material';
21
import { makeStyles } from 'tss-react/mui';
22

23
import moment from 'moment';
24

25
import { setSnackbar } from '../../actions/appActions';
26
import { getDeploymentsByStatus, setDeploymentsState } from '../../actions/deploymentActions';
27
import { DEPLOYMENT_STATES } from '../../constants/deploymentConstants';
28
import { tryMapDeployments } from '../../helpers';
29
import { getIdAttribute, getIsEnterprise, getUserCapabilities } from '../../selectors';
30
import { clearAllRetryTimers, clearRetryTimer, setRetryTimer } from '../../utils/retrytimer';
31
import EnterpriseNotification from '../common/enterpriseNotification';
32
import { DeploymentDeviceCount, DeploymentEndTime, DeploymentPhases, DeploymentStartTime } from './deploymentitem';
33
import { defaultRefreshDeploymentsLength as refreshDeploymentsLength } from './deployments';
34
import DeploymentsList, { defaultHeaders } from './deploymentslist';
35

36
const useStyles = makeStyles()(theme => ({
7✔
37
  inactive: { color: theme.palette.text.disabled },
38
  refreshIcon: { fill: theme.palette.grey[400], width: 111, height: 111 },
39
  tabSelect: { textTransform: 'none' }
40
}));
41

42
const localizer = momentLocalizer(moment);
7✔
43

44
const headers = [
7✔
45
  ...defaultHeaders.slice(0, 2),
46
  { title: 'Start time', renderer: DeploymentStartTime, props: { direction: 'up' } },
47
  { title: `End time`, renderer: DeploymentEndTime },
48
  { title: '# devices', class: 'align-right column-defined', renderer: DeploymentDeviceCount },
49
  { title: 'Phases', renderer: DeploymentPhases }
50
];
51

52
const tabs = {
7✔
53
  list: {
54
    icon: <ListIcon />,
55
    index: 'list',
56
    title: 'List view'
57
  },
58
  calendar: {
59
    icon: <CalendarTodayIcon />,
60
    index: 'calendar',
61
    title: 'Calendar'
62
  }
63
};
64

65
const type = DEPLOYMENT_STATES.scheduled;
7✔
66

67
export const Scheduled = props => {
7✔
68
  const [calendarEvents, setCalendarEvents] = useState([]);
3✔
69
  const [tabIndex, setTabIndex] = useState(tabs.list.index);
3✔
70
  const timer = useRef();
3✔
71

72
  const { classes } = useStyles();
3✔
73

74
  const { abort, canDeploy, createClick, getDeploymentsByStatus, isEnterprise, items, openReport, scheduledState, setDeploymentsState, setSnackbar } = props;
3✔
75
  const { page, perPage } = scheduledState;
3✔
76

77
  useEffect(() => {
3✔
78
    if (!isEnterprise) {
2!
79
      return;
×
80
    }
81
    refreshDeployments();
2✔
82
    return () => {
2✔
83
      clearAllRetryTimers(setSnackbar);
2✔
84
    };
85
  }, [isEnterprise]);
86

87
  useEffect(() => {
3✔
88
    if (!isEnterprise) {
2!
89
      return;
×
90
    }
91
    clearInterval(timer.current);
2✔
92
    timer.current = setInterval(refreshDeployments, refreshDeploymentsLength);
2✔
93
    return () => {
2✔
94
      clearInterval(timer.current);
2✔
95
    };
96
  }, [isEnterprise, page, perPage]);
97

98
  useEffect(() => {
3✔
99
    if (tabIndex !== tabs.calendar.index) {
2!
100
      return;
2✔
101
    }
102
    const calendarEvents = items.map(deployment => {
×
103
      const start = new Date(deployment.start_ts || deployment.phases ? deployment.phases[0].start_ts : deployment.created);
×
104
      let endDate = start;
×
105
      if (deployment.phases && deployment.phases.length && deployment.phases[deployment.phases.length - 1].end_ts) {
×
106
        endDate = new Date(deployment.phases[deployment.phases.length - 1].end_ts);
×
107
      } else if (deployment.filter_id || deployment.filter) {
×
108
        // calendar doesn't support never ending events so we arbitrarly set one year
109
        endDate = moment(start).add(1, 'year');
×
110
      }
111
      return {
×
112
        allDay: !(deployment.filter_id || deployment.filter),
×
113
        id: deployment.id,
114
        title: `${deployment.name} ${deployment.artifact_name}`,
115
        start,
116
        end: endDate
117
      };
118
    });
119
    setCalendarEvents(calendarEvents);
×
120
  }, [tabIndex]);
121

122
  const refreshDeployments = useCallback(() => {
3✔
123
    return getDeploymentsByStatus(DEPLOYMENT_STATES.scheduled, page, perPage)
2✔
124
      .then(deploymentsAction => {
125
        clearRetryTimer(type, setSnackbar);
1✔
126
        const { total, deploymentIds } = deploymentsAction[deploymentsAction.length - 1];
1✔
127
        if (total && !deploymentIds.length) {
1!
128
          return refreshDeployments();
×
129
        }
130
      })
131
      .catch(err => setRetryTimer(err, 'deployments', `Couldn't load deployments.`, refreshDeploymentsLength, setSnackbar));
×
132
  }, [page, perPage]);
133

134
  const abortDeployment = id => abort(id).then(refreshDeployments);
3✔
135

136
  return (
3✔
137
    <div className="fadeIn margin-left">
138
      {items.length ? (
3✔
139
        <>
140
          <div className="margin-large margin-left-small">
141
            {Object.entries(tabs).map(([currentIndex, tab]) => (
142
              <Button
4✔
143
                className={`${classes.tabSelect} ${currentIndex !== tabIndex ? classes.inactive : ''}`}
4✔
144
                color="primary"
145
                key={currentIndex}
146
                startIcon={tab.icon}
147
                onClick={() => setTabIndex(currentIndex)}
×
148
              >
149
                {tab.title}
150
              </Button>
151
            ))}
152
          </div>
153
          {tabIndex === tabs.list.index && (
4✔
154
            <DeploymentsList
155
              {...props}
156
              abort={abortDeployment}
157
              headers={headers}
158
              type={type}
159
              onChangeRowsPerPage={perPage => setDeploymentsState({ [DEPLOYMENT_STATES.scheduled]: { page: 1, perPage } })}
×
160
              onChangePage={page => setDeploymentsState({ [DEPLOYMENT_STATES.scheduled]: { page } })}
×
161
            />
162
          )}
163
          {tabIndex === tabs.calendar.index && (
2!
164
            <Calendar
165
              localizer={localizer}
166
              className="margin-left margin-bottom"
167
              events={calendarEvents}
168
              startAccessor="start"
169
              endAccessor="end"
170
              style={{ height: 700 }}
171
              onSelectEvent={calendarEvent => openReport(type, calendarEvent.id)}
×
172
            />
173
          )}
174
        </>
175
      ) : (
176
        <div className="dashboard-placeholder margin-top">
177
          {isEnterprise ? (
1!
178
            <>
179
              <p>Scheduled deployments will appear here. </p>
180
              {canDeploy && (
2✔
181
                <p>
182
                  <a onClick={createClick}>Create a deployment</a> to get started
183
                </p>
184
              )}
185
            </>
186
          ) : (
187
            <div className="flexbox centered">
188
              <EnterpriseNotification isEnterprise={isEnterprise} benefit="scheduled deployments to steer the distribution of your updates." />
189
            </div>
190
          )}
191
          <RefreshIcon className={`flip-horizontal ${classes.refreshIcon}`} />
192
        </div>
193
      )}
194
    </div>
195
  );
196
};
197

198
const actionCreators = { getDeploymentsByStatus, setSnackbar, setDeploymentsState };
7✔
199

200
const mapStateToProps = state => {
7✔
201
  const scheduled = state.deployments.selectionState.scheduled.selection.reduce(tryMapDeployments, { state, deployments: [] }).deployments;
3✔
202
  const { plan = 'os' } = state.organization.organization;
3!
203
  const { canConfigure, canDeploy } = getUserCapabilities(state);
3✔
204
  return {
3✔
205
    canConfigure,
206
    canDeploy,
207
    count: state.deployments.byStatus.scheduled.total,
208
    devices: state.devices.byId,
209
    idAttribute: getIdAttribute(state).attribute,
210
    // TODO: isEnterprise is misleading here, but is passed down to the DeploymentListItem, this should be renamed
211
    isEnterprise: getIsEnterprise(state) || plan !== 'os',
3!
212
    items: scheduled,
213
    scheduledState: state.deployments.selectionState.scheduled
214
  };
215
};
216

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