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

mendersoftware / gui / 1081664682

22 Nov 2023 02:11PM UTC coverage: 82.798% (-17.2%) from 99.964%
1081664682

Pull #4214

gitlab-ci

tranchitella
fix: Fixed the infinite page redirects when the back button is pressed

Remove the location and navigate from the useLocationParams.setValue callback
dependencies as they change the set function that is presented in other
useEffect dependencies. This happens when the back button is clicked, which
leads to the location changing infinitely.

Changelog: Title
Ticket: MEN-6847
Ticket: MEN-6796

Signed-off-by: Ihor Aleksandrychiev <ihor.aleksandrychiev@northern.tech>
Signed-off-by: Fabio Tranchitella <fabio.tranchitella@northern.tech>
Pull Request #4214: fix: Fixed the infinite page redirects when the back button is pressed

4319 of 6292 branches covered (0.0%)

8332 of 10063 relevant lines covered (82.8%)

191.0 hits per line

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

87.5
/src/js/components/devices/device-details/deployments.js
1
// Copyright 2023 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, useState } from 'react';
15
import { useDispatch } from 'react-redux';
16
import { Link } from 'react-router-dom';
17

18
import { Button, Table, TableBody, TableCell, TableHead, TableRow, buttonClasses, tableCellClasses } from '@mui/material';
19
import { makeStyles } from 'tss-react/mui';
20

21
import { deploymentsApiUrl, getDeviceDeployments, resetDeviceDeployments } from '../../../actions/deploymentActions';
22
import { deploymentDisplayStates, deploymentStatesToSubstates } from '../../../constants/deploymentConstants';
23
import { DEVICE_LIST_DEFAULTS } from '../../../constants/deviceConstants';
24
import Confirm from '../../common/confirm';
25
import Pagination from '../../common/pagination';
26
import { MaybeTime } from '../../common/time';
27
import { HELPTOOLTIPS, MenderHelpTooltip } from '../../helptips/helptooltips';
28
import { DeviceStateSelection } from '../authorized-devices';
29

30
const useStyles = makeStyles()(theme => ({
8✔
31
  deletion: { justifyContent: 'flex-end' },
32
  table: {
33
    minHeight: '10vh',
34
    [`.deleted > .${tableCellClasses.root}, .deleted a`]: {
35
      background: theme.palette.background.lightgrey,
36
      color: theme.palette.grey[700],
37
      [`.${buttonClasses.root}`]: { color: theme.palette.text.primary }
38
    }
39
  }
40
}));
41

42
const EmptyState = ({ isFiltered }) => (
8✔
43
  <div className="flexbox column centered margin-large">
×
44
    <p className="align-center muted">
45
      No deployments were found.
46
      <br />
47
      {isFiltered && <>Try adjusting the filters</>}
×
48
    </p>
49
  </div>
50
);
51

52
const columns = [
8✔
53
  { content: 'Release', key: 'release', Component: ({ deviceDeployment: { release } }) => <Link to={`/releases/${release}`}>{release}</Link> },
2✔
54
  {
55
    content: 'Target device(s)',
56
    key: 'target',
57
    Component: ({ deviceDeployment: { id, target, route } }) => <Link to={`/deployments/${route}?id=${id}&open=true`}>{target}</Link>
2✔
58
  },
59
  { content: 'Started', key: 'created', Component: ({ deviceDeployment: { created } }) => <MaybeTime value={created} /> },
2✔
60
  { content: 'Finished', key: 'finished', Component: ({ deviceDeployment: { finished } }) => <MaybeTime value={finished} /> },
2✔
61
  {
62
    content: 'Deployment progress',
63
    key: 'deploymentStatus',
64
    Component: ({ deviceDeployment: { deploymentStatus } }) => deploymentDisplayStates[deploymentStatus] ?? deploymentStatus
2!
65
  },
66
  { content: 'Device status', key: 'status', Component: ({ deviceDeployment: { status } }) => status },
2✔
67
  {
68
    content: '',
69
    key: 'log',
70
    Component: ({ deviceDeployment: { id, log, deviceId } }) =>
71
      log && (
2✔
72
        <Button component="a" href={`${window.location.origin}${deploymentsApiUrl}/deployments/${id}/devices/${deviceId}/log`} target="_blank">
73
          Log
74
        </Button>
75
      )
76
  }
77
];
78

79
const History = ({ className, items, page, perPage, setPage, setPerPage, total }) => {
8✔
80
  const onChangeRowsPerPage = perPage => {
2✔
81
    setPage(1);
×
82
    setPerPage(perPage);
×
83
  };
84
  const wasReset = items.reduce((accu, { deleted }) => {
2✔
85
    if (!accu) {
2!
86
      return !!deleted;
2✔
87
    }
88
    return accu;
×
89
  }, false);
90
  return (
2✔
91
    <div className={className}>
92
      <Table>
93
        <TableHead>
94
          <TableRow>
95
            {columns.map(({ content, key }) => (
96
              <TableCell key={key}>{content}</TableCell>
14✔
97
            ))}
98
          </TableRow>
99
        </TableHead>
100
        <TableBody>
101
          {items.map(item => (
102
            <TableRow className={item.deleted ? 'deleted' : ''} key={item.id}>
2!
103
              {columns.map(({ key, Component }) => (
104
                <TableCell key={`${item.id}-${key}`}>
14✔
105
                  <Component deviceDeployment={item} />
106
                </TableCell>
107
              ))}
108
            </TableRow>
109
          ))}
110
        </TableBody>
111
      </Table>
112
      <div className="flexbox space-between">
113
        <Pagination
114
          count={total}
115
          onChangePage={setPage}
116
          onChangeRowsPerPage={onChangeRowsPerPage}
117
          page={page}
118
          rowsPerPage={perPage}
119
          rowsPerPageOptions={[10, 20]}
120
        />
121
        {wasReset && <MenderHelpTooltip id={HELPTOOLTIPS.resetHistory.id} />}
2!
122
      </div>
123
    </div>
124
  );
125
};
126

127
const deploymentStates = {
8✔
128
  any: { key: 'any', title: () => 'any', values: [] },
2✔
129
  pending: { key: 'pending', title: () => 'pending', values: deploymentStatesToSubstates.pending },
2✔
130
  inprogress: { key: 'inprogress', title: () => 'in progress', values: deploymentStatesToSubstates.inprogress },
2✔
131
  paused: { key: 'paused', title: () => 'paused', values: deploymentStatesToSubstates.paused },
2✔
132
  failures: { key: 'failures', title: () => 'failures', values: deploymentStatesToSubstates.failures },
2✔
133
  successes: { key: 'successes', title: () => 'successes', values: deploymentStatesToSubstates.successes }
2✔
134
};
135

136
export const Deployments = ({ device }) => {
8✔
137
  const [filters, setFilters] = useState([deploymentStates.any.key]);
2✔
138
  const [page, setPage] = useState(DEVICE_LIST_DEFAULTS.page);
2✔
139
  const [perPage, setPerPage] = useState(10);
2✔
140
  const [isChecking, setIsChecking] = useState(false);
2✔
141
  const { classes } = useStyles();
2✔
142
  const dispatch = useDispatch();
2✔
143

144
  useEffect(() => {
2✔
145
    if (!device?.id) {
2!
146
      return;
×
147
    }
148
    const filterSelection = deploymentStates[filters[0]].values;
2✔
149
    dispatch(getDeviceDeployments(device.id, { filterSelection, page, perPage }));
2✔
150
  }, [device.id, dispatch, filters, page, perPage]);
151

152
  const onSelectStatus = status => setFilters([status]);
2✔
153

154
  const onResetStart = () => setIsChecking(true);
2✔
155

156
  const onResetConfirm = () => dispatch(resetDeviceDeployments(device.id)).then(() => setIsChecking(false));
2✔
157

158
  const { deviceDeployments = [], deploymentsCount } = device;
2!
159

160
  return (
2✔
161
    <div className="margin-bottom">
162
      <h4 className="margin-bottom-small">Deployments</h4>
163
      <div className="flexbox margin-bottom-small" style={{ alignSelf: 'flex-start' }}>
164
        <DeviceStateSelection onStateChange={onSelectStatus} selectedState={filters[0]} states={deploymentStates} />
165
      </div>
166

167
      {!deviceDeployments.length ? (
2!
168
        <EmptyState isFiltered={filters[0] !== deploymentStates.any.key} />
169
      ) : (
170
        <>
171
          <History
172
            className={classes.table}
173
            items={deviceDeployments}
174
            page={page}
175
            perPage={perPage}
176
            setPage={setPage}
177
            setPerPage={setPerPage}
178
            total={deploymentsCount}
179
          />
180
          <div className={`flexbox margin-top relative ${classes.deletion}`}>
181
            {isChecking && (
2!
182
              <Confirm
183
                classes="confirmation-overlay"
184
                cancel={() => setIsChecking(false)}
×
185
                action={onResetConfirm}
186
                message={`This will reset the stored device deployment history for this device. Are you sure?`}
187
                style={{ marginRight: 0 }}
188
              />
189
            )}
190
            <Button onClick={onResetStart} variant="contained">
191
              Reset device deployment history
192
            </Button>
193
          </div>
194
        </>
195
      )}
196
    </div>
197
  );
198
};
199

200
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