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

mendersoftware / gui / 1113439055

19 Dec 2023 09:01PM UTC coverage: 82.752% (-17.2%) from 99.964%
1113439055

Pull #4258

gitlab-ci

mender-test-bot
chore: Types update

Signed-off-by: Mender Test Bot <mender@northern.tech>
Pull Request #4258: chore: Types update

4326 of 6319 branches covered (0.0%)

8348 of 10088 relevant lines covered (82.75%)

189.39 hits per line

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

81.94
/src/js/components/deployments/pastdeployments.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, { useCallback, useEffect, useRef, useState } from 'react';
15
import { useDispatch, useSelector } from 'react-redux';
16

17
// material ui
18
import { TextField } from '@mui/material';
19

20
import historyImage from '../../../assets/img/history.png';
21
import { setSnackbar } from '../../actions/appActions';
22
import { getDeploymentsByStatus, setDeploymentsState } from '../../actions/deploymentActions';
23
import { advanceOnboarding } from '../../actions/onboardingActions';
24
import { BEGINNING_OF_TIME, SORTING_OPTIONS } from '../../constants/appConstants';
25
import { DEPLOYMENT_STATES, DEPLOYMENT_TYPES } from '../../constants/deploymentConstants';
26
import { onboardingSteps } from '../../constants/onboardingConstants';
27
import { getISOStringBoundaries } from '../../helpers';
28
import {
29
  getDeploymentsSelectionState,
30
  getDevicesById,
31
  getGroupNames,
32
  getIdAttribute,
33
  getMappedDeploymentSelection,
34
  getOnboardingState,
35
  getUserCapabilities
36
} from '../../selectors';
37
import { getOnboardingComponentFor } from '../../utils/onboardingmanager';
38
import useWindowSize from '../../utils/resizehook';
39
import { clearAllRetryTimers, clearRetryTimer, setRetryTimer } from '../../utils/retrytimer';
40
import { ControlledAutoComplete } from '../common/forms/autocomplete';
41
import Filters from '../common/forms/filters';
42
import TimeframePicker from '../common/forms/timeframe-picker';
43
import { DeploymentSize, DeploymentStatus } from './deploymentitem';
44
import { defaultRefreshDeploymentsLength as refreshDeploymentsLength } from './deployments';
45
import DeploymentsList, { defaultHeaders } from './deploymentslist';
46

47
const headers = [
6✔
48
  ...defaultHeaders.slice(0, defaultHeaders.length - 1),
49
  { title: 'Status', renderer: DeploymentStatus },
50
  { title: 'Data downloaded', renderer: DeploymentSize }
51
];
52

53
const type = DEPLOYMENT_STATES.finished;
6✔
54

55
export const Past = props => {
6✔
56
  const { createClick, isShowingDetails } = props;
66✔
57
  // eslint-disable-next-line no-unused-vars
58
  const size = useWindowSize();
65✔
59
  const [tonight] = useState(getISOStringBoundaries(new Date()).end);
65✔
60
  const [loading, setLoading] = useState(false);
65✔
61
  const deploymentsRef = useRef();
65✔
62
  const timer = useRef();
65✔
63

64
  const dispatch = useDispatch();
65✔
65
  const dispatchedSetSnackbar = useCallback((...args) => dispatch(setSnackbar(...args)), [dispatch]);
65✔
66

67
  const { finished: pastSelectionState } = useSelector(getDeploymentsSelectionState);
65✔
68
  const past = useSelector(state => getMappedDeploymentSelection(state, type));
123✔
69
  const { canConfigure, canDeploy } = useSelector(getUserCapabilities);
65✔
70
  const { attribute: idAttribute } = useSelector(getIdAttribute);
65✔
71
  const onboardingState = useSelector(getOnboardingState);
65✔
72
  const devices = useSelector(getDevicesById);
65✔
73
  const groupNames = useSelector(getGroupNames);
65✔
74

75
  const { endDate, page, perPage, search: deviceGroup, startDate, total: count, type: deploymentType } = pastSelectionState;
65✔
76

77
  /*
78
  / refresh only finished deployments
79
  /
80
  */
81
  const refreshPast = useCallback(
65✔
82
    (
83
      currentPage = page,
×
84
      currentPerPage = perPage,
×
85
      currentStartDate = startDate,
×
86
      currentEndDate = endDate,
×
87
      currentDeviceGroup = deviceGroup,
×
88
      currentType = deploymentType
×
89
    ) => {
90
      const roundedStartDate = Math.round(Date.parse(currentStartDate) / 1000);
×
91
      const roundedEndDate = Math.round(Date.parse(currentEndDate) / 1000);
×
92
      setLoading(true);
×
93
      return dispatch(getDeploymentsByStatus(type, currentPage, currentPerPage, roundedStartDate, roundedEndDate, currentDeviceGroup, currentType))
×
94
        .then(deploymentsAction => {
95
          setLoading(false);
×
96
          clearRetryTimer(type, dispatchedSetSnackbar);
×
97
          const { total, deploymentIds } = deploymentsAction[deploymentsAction.length - 1];
×
98
          if (total && !deploymentIds.length) {
×
99
            return refreshPast(currentPage, currentPerPage, currentStartDate, currentEndDate, currentDeviceGroup);
×
100
          }
101
        })
102
        .catch(err => setRetryTimer(err, 'deployments', `Couldn't load deployments.`, refreshDeploymentsLength, dispatchedSetSnackbar));
×
103
    },
104
    [deploymentType, deviceGroup, dispatch, dispatchedSetSnackbar, endDate, page, perPage, startDate]
105
  );
106

107
  useEffect(() => {
65✔
108
    const roundedStartDate = Math.round(Date.parse(startDate || BEGINNING_OF_TIME) / 1000);
4✔
109
    const roundedEndDate = Math.round(Date.parse(endDate) / 1000);
4✔
110
    setLoading(true);
4✔
111
    dispatch(getDeploymentsByStatus(type, page, perPage, roundedStartDate, roundedEndDate, deviceGroup, deploymentType, true, SORTING_OPTIONS.desc))
4✔
112
      .then(deploymentsAction => {
113
        const deploymentsList = deploymentsAction ? Object.values(deploymentsAction[0].deployments) : [];
3!
114
        if (deploymentsList.length) {
3!
115
          let newStartDate = new Date(deploymentsList[deploymentsList.length - 1].created);
3✔
116
          const { start } = getISOStringBoundaries(newStartDate);
3✔
117
          dispatch(setDeploymentsState({ [DEPLOYMENT_STATES.finished]: { startDate: startDate || start } }));
3✔
118
        }
119
      })
120
      .finally(() => setLoading(false));
3✔
121
    return () => {
4✔
122
      clearAllRetryTimers(dispatchedSetSnackbar);
4✔
123
    };
124
  }, [deploymentType, deviceGroup, dispatch, dispatchedSetSnackbar, endDate, page, perPage, startDate]);
125

126
  useEffect(() => {
65✔
127
    clearInterval(timer.current);
4✔
128
    timer.current = setInterval(refreshPast, refreshDeploymentsLength);
4✔
129
    // refreshPast();
130
    return () => {
4✔
131
      clearInterval(timer.current);
4✔
132
    };
133
  }, [page, perPage, startDate, endDate, deviceGroup, deploymentType, refreshPast]);
134

135
  useEffect(() => {
65✔
136
    if (!past.length || onboardingState.complete) {
28✔
137
      return;
22✔
138
    }
139
    const pastDeploymentsFailed = past.reduce(
6✔
140
      (accu, item) =>
141
        item.status === 'failed' ||
8✔
142
        (item.statistics?.status &&
143
          item.statistics.status.noartifact + item.statistics.status.failure + item.statistics.status['already-installed'] + item.statistics.status.aborted >
144
            0) ||
145
        accu,
146
      false
147
    );
148
    let onboardingStep = onboardingSteps.DEPLOYMENTS_PAST;
6✔
149
    if (pastDeploymentsFailed) {
6!
150
      onboardingStep = onboardingSteps.DEPLOYMENTS_PAST_COMPLETED_FAILURE;
×
151
    }
152
    dispatch(advanceOnboarding(onboardingStep));
6✔
153
  }, [dispatch, onboardingState.complete, past]);
154

155
  let onboardingComponent = null;
65✔
156
  if (deploymentsRef.current) {
65✔
157
    const detailsButtons = deploymentsRef.current.getElementsByClassName('MuiButton-contained');
37✔
158
    const left = detailsButtons.length
37!
159
      ? deploymentsRef.current.offsetLeft + detailsButtons[0].offsetLeft + detailsButtons[0].offsetWidth / 2 + 15
160
      : deploymentsRef.current.offsetWidth;
161
    let anchor = { left: deploymentsRef.current.offsetWidth / 2, top: deploymentsRef.current.offsetTop };
37✔
162
    onboardingComponent = getOnboardingComponentFor(onboardingSteps.DEPLOYMENTS_PAST_COMPLETED, onboardingState, {
37✔
163
      anchor,
164
      setSnackbar: dispatchedSetSnackbar
165
    });
166
    onboardingComponent = getOnboardingComponentFor(
37✔
167
      onboardingSteps.DEPLOYMENTS_PAST_COMPLETED_FAILURE,
168
      onboardingState,
169
      { anchor: { left, top: detailsButtons[0].parentElement.offsetTop + detailsButtons[0].parentElement.offsetHeight } },
170
      onboardingComponent
171
    );
172
  }
173

174
  const onFiltersChange = useCallback(
65✔
175
    ({ endDate, group, startDate, type }) =>
176
      dispatch(setDeploymentsState({ [DEPLOYMENT_STATES.finished]: { page: 1, search: group, type, startDate, endDate } })),
4✔
177
    [dispatch]
178
  );
179

180
  const autoCompleteProps = { autoHighlight: true, autoSelect: true, filterSelectedOptions: true, freeSolo: true, handleHomeEndKeys: true };
65✔
181
  return (
65✔
182
    <div className="fadeIn margin-left margin-top-large">
183
      <Filters
184
        initialValues={{ startDate, endDate, group: deviceGroup, type: deploymentType }}
185
        defaultValues={{ startDate: '', endDate: tonight, group: '', type: '' }}
186
        filters={[
187
          {
188
            key: 'group',
189
            title: 'Device group',
190
            Component: ControlledAutoComplete,
191
            componentProps: {
192
              ...autoCompleteProps,
193
              options: groupNames,
194
              renderInput: params => <TextField {...params} label="Target devices" placeholder="Select a group" InputProps={{ ...params.InputProps }} />
66✔
195
            }
196
          },
197
          {
198
            key: 'type',
199
            title: 'Contains Artifact type',
200
            Component: ControlledAutoComplete,
201
            componentProps: {
202
              ...autoCompleteProps,
203
              options: Object.keys(DEPLOYMENT_TYPES),
204
              renderInput: params => <TextField {...params} label="Deployment type" placeholder="Select a type" InputProps={{ ...params.InputProps }} />
66✔
205
            }
206
          },
207
          {
208
            key: 'timeframe',
209
            title: 'Start time',
210
            Component: TimeframePicker,
211
            componentProps: {
212
              tonight
213
            }
214
          }
215
        ]}
216
        onChange={onFiltersChange}
217
      />
218
      <div className="deploy-table-contain">
219
        {/* TODO: fix status retrieval for past deployments to decide what to show here - */}
220
        {!loading && !!past.length && !!onboardingComponent && !isShowingDetails && onboardingComponent}
110!
221
        {!!past.length && (
105✔
222
          <DeploymentsList
223
            {...props}
224
            canConfigure={canConfigure}
225
            canDeploy={canDeploy}
226
            componentClass="margin-left-small"
227
            count={count}
228
            devices={devices}
229
            headers={headers}
230
            idAttribute={idAttribute}
231
            items={past}
232
            loading={loading}
233
            onChangePage={page => dispatch(setDeploymentsState({ [DEPLOYMENT_STATES.finished]: { page } }))}
×
234
            onChangeRowsPerPage={perPage => dispatch(setDeploymentsState({ [DEPLOYMENT_STATES.finished]: { page: 1, perPage } }))}
×
235
            page={page}
236
            pageSize={perPage}
237
            rootRef={deploymentsRef}
238
            showPagination
239
            type={type}
240
          />
241
        )}
242
        {!(loading || past.length) && (
154✔
243
          <div className="dashboard-placeholder">
244
            <p>No finished deployments were found.</p>
245
            <p>
246
              Try adjusting the filters, or <a onClick={createClick}>Create a new deployment</a> to get started
247
            </p>
248
            <img src={historyImage} alt="Past" />
249
          </div>
250
        )}
251
      </div>
252
    </div>
253
  );
254
};
255

256
export default Past;
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