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

mendersoftware / mender-server / 1585855791

12 Dec 2024 01:05PM UTC coverage: 73.521% (+0.7%) from 72.806%
1585855791

Pull #262

gitlab-ci

aleksandrychev
fix(gui): fixed end date filters out today's logs in the Audit log

Ticket: ME-389

ChangeLog: Title
Signed-off-by: Ihor Aleksandrychiev <ihor.aleksandrychiev@northern.tech>
Pull Request #262: fix(gui): fixed end date filters out today's logs in the Audit log

4240 of 6143 branches covered (69.02%)

Branch coverage included in aggregate %.

6 of 6 new or added lines in 2 files covered. (100.0%)

57 existing lines in 3 files now uncovered.

40044 of 54090 relevant lines covered (74.03%)

23.11 hits per line

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

89.66
/frontend/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 { ControlledAutoComplete } from '@northern.tech/common-ui/forms/autocomplete';
21
import Filters from '@northern.tech/common-ui/forms/filters';
22
import TimeframePicker from '@northern.tech/common-ui/forms/timeframe-picker';
23
import storeActions from '@northern.tech/store/actions';
24
import { BEGINNING_OF_TIME, DEPLOYMENT_STATES, DEPLOYMENT_TYPES, onboardingSteps } from '@northern.tech/store/constants';
25
import {
26
  getDeploymentsSelectionState,
27
  getDevicesById,
28
  getGroupNames,
29
  getIdAttribute,
30
  getMappedDeploymentSelection,
31
  getOnboardingState,
32
  getUserCapabilities
33
} from '@northern.tech/store/selectors';
34
import { advanceOnboarding, getDeploymentsByStatus, setDeploymentsState } from '@northern.tech/store/thunks';
35
import { getISOStringBoundaries } from '@northern.tech/utils/helpers';
36
import useWindowSize from '@northern.tech/utils/resizehook';
37
import { clearAllRetryTimers, clearRetryTimer, setRetryTimer } from '@northern.tech/utils/retrytimer';
38

39
import historyImage from '../../../assets/img/history.png';
40
import { getOnboardingComponentFor } from '../../utils/onboardingmanager';
41
import { DeploymentSize, DeploymentStatus } from './deploymentitem';
42
import { defaultRefreshDeploymentsLength as refreshDeploymentsLength } from './deployments';
43
import DeploymentsList, { defaultHeaders } from './deploymentslist';
44

45
const { setSnackbar } = storeActions;
6✔
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;
36✔
57
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
58
  const size = useWindowSize();
36✔
59
  const [tonight] = useState(getISOStringBoundaries(new Date()).end);
36✔
60
  const [loading, setLoading] = useState(false);
36✔
61
  const deploymentsRef = useRef();
36✔
62
  const timer = useRef();
36✔
63

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

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

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

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

117
  useEffect(() => {
36✔
118
    const roundedStartDate = Math.round(Date.parse(startDate || BEGINNING_OF_TIME) / 1000);
5✔
119
    const roundedEndDate = Math.round(Date.parse(endDate) / 1000);
5✔
120
    setLoading(true);
5✔
121
    dispatch(
5✔
122
      getDeploymentsByStatus({ status: type, page, perPage, startDate: roundedStartDate, endDate: roundedEndDate, group: deviceGroup, type: deploymentType })
123
    )
124
      .then(deploymentsAction => {
125
        const deploymentsList = deploymentsAction ? Object.values(deploymentsAction.payload[0]) : [];
5!
126
        if (deploymentsList.length) {
5!
127
          let newStartDate = new Date(deploymentsList[deploymentsList.length - 1].created);
5✔
128
          const { start } = getISOStringBoundaries(newStartDate);
5✔
129
          dispatch(setDeploymentsState({ [DEPLOYMENT_STATES.finished]: { startDate: startDate || start } }));
5✔
130
        }
131
      })
132
      .finally(() => setLoading(false));
5✔
133
    return () => {
5✔
134
      clearAllRetryTimers(dispatchedSetSnackbar);
5✔
135
    };
136
  }, [deploymentType, deviceGroup, dispatch, dispatchedSetSnackbar, endDate, page, perPage, startDate]);
137

138
  useEffect(() => {
36✔
139
    clearInterval(timer.current);
5✔
140
    timer.current = setInterval(refreshPast, refreshDeploymentsLength);
5✔
141
    // refreshPast();
142
    return () => {
5✔
143
      clearInterval(timer.current);
5✔
144
    };
145
  }, [page, perPage, startDate, endDate, deviceGroup, deploymentType, refreshPast]);
146

147
  useEffect(() => {
36✔
148
    if (!past.length || onboardingState.complete) {
14✔
149
      return;
4✔
150
    }
151
    const pastDeploymentsFailed = past.reduce(
10✔
152
      (accu, item) =>
153
        item.status === 'failed' ||
16✔
154
        (item.statistics?.status &&
155
          item.statistics.status.noartifact + item.statistics.status.failure + item.statistics.status['already-installed'] + item.statistics.status.aborted >
156
            0) ||
157
        accu,
158
      false
159
    );
160
    let onboardingStep = onboardingSteps.DEPLOYMENTS_PAST;
10✔
161
    if (pastDeploymentsFailed) {
10!
UNCOV
162
      onboardingStep = onboardingSteps.DEPLOYMENTS_PAST_COMPLETED_FAILURE;
×
163
    }
164
    dispatch(advanceOnboarding(onboardingStep));
10✔
165
  }, [dispatch, onboardingState.complete, past]);
166

167
  let onboardingComponent = null;
36✔
168
  if (deploymentsRef.current) {
36✔
169
    const detailsButtons = deploymentsRef.current.getElementsByClassName('MuiButton-contained');
24✔
170
    const left = detailsButtons.length
24!
171
      ? deploymentsRef.current.offsetLeft + detailsButtons[0].offsetLeft + detailsButtons[0].offsetWidth / 2 + 15
172
      : deploymentsRef.current.offsetWidth;
173
    let anchor = { left: deploymentsRef.current.offsetWidth / 2, top: deploymentsRef.current.offsetTop };
24✔
174
    onboardingComponent = getOnboardingComponentFor(onboardingSteps.DEPLOYMENTS_PAST_COMPLETED, onboardingState, {
24✔
175
      anchor,
176
      setSnackbar: dispatchedSetSnackbar
177
    });
178
    onboardingComponent = getOnboardingComponentFor(
24✔
179
      onboardingSteps.DEPLOYMENTS_PAST_COMPLETED_FAILURE,
180
      onboardingState,
181
      { anchor: { left, top: detailsButtons[0].parentElement.offsetTop + detailsButtons[0].parentElement.offsetHeight } },
182
      onboardingComponent
183
    );
184
  }
185

186
  const onFiltersChange = useCallback(
36✔
187
    ({ endDate, group, startDate, type }) =>
188
      dispatch(setDeploymentsState({ [DEPLOYMENT_STATES.finished]: { page: 1, search: group, type, startDate, endDate } })),
4✔
189
    [dispatch]
190
  );
191

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

268
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