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

mendersoftware / gui / 1350829378

27 Jun 2024 01:46PM UTC coverage: 83.494% (-16.5%) from 99.965%
1350829378

Pull #4465

gitlab-ci

mzedel
chore: test fixes

Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #4465: MEN-7169 - feat: added multi sorting capabilities to devices view

4506 of 6430 branches covered (70.08%)

81 of 100 new or added lines in 14 files covered. (81.0%)

1661 existing lines in 163 files now uncovered.

8574 of 10269 relevant lines covered (83.49%)

160.6 hits per line

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

85.71
/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 { getToken } from '../../../auth.js';
23
import { deploymentStatesToSubstates } from '../../../constants/deploymentConstants';
24
import { DEVICE_LIST_DEFAULTS } from '../../../constants/deviceConstants';
25
import { createDownload } from '../../../helpers.js';
26
import Confirm from '../../common/confirm';
27
import InfoHint from '../../common/info-hint';
28
import Pagination from '../../common/pagination';
29
import { MaybeTime } from '../../common/time';
30
import { HELPTOOLTIPS, MenderHelpTooltip } from '../../helptips/helptooltips';
31
import { DeviceStateSelection } from '../authorized-devices';
32

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

45
const EmptyState = ({ isFiltered }) => (
8✔
UNCOV
46
  <>
×
47
    <div className="flexbox column centered margin-large">
48
      <p className="align-center muted">
49
        No deployments were found.
50
        <br />
51
        {isFiltered && <>Try adjusting the filters</>}
×
52
      </p>
53
    </div>
54
    <InfoHint content="If this device is part of a pending or scheduled deployment, the deployment will only appear here once it has started and the device has reported its update status to the server." />
55
  </>
56
);
57

58
const columns = [
8✔
59
  { content: 'Release', key: 'release', Component: ({ deviceDeployment: { release } }) => <Link to={`/releases/${release}`}>{release}</Link> },
2✔
60
  { content: 'Started', key: 'created', Component: ({ deviceDeployment: { created } }) => <MaybeTime value={created} /> },
2✔
61
  { content: 'Finished', key: 'finished', Component: ({ deviceDeployment: { finished } }) => <MaybeTime value={finished} /> },
2✔
62
  { content: 'Device status', key: 'status', Component: ({ deviceDeployment: { status } }) => status },
2✔
63
  {
64
    content: '',
65
    key: 'log',
66
    Component: ({ deviceDeployment: { id, log, deviceId }, token }) =>
67
      log && (
2✔
68
        <Button
69
          onClick={() =>
UNCOV
70
            createDownload(
×
71
              ` ${window.location.origin}${deploymentsApiUrl}/deployments/${id}/devices/${deviceId}/log`,
72
              `device_${deviceId}_deployment_${id}.log`,
73
              token
74
            )
75
          }
76
        >
77
          Log
78
        </Button>
79
      )
80
  },
81
  {
82
    content: '',
83
    key: 'target',
84
    Component: ({ deviceDeployment: { id, route } }) => <Link to={`/deployments/${route}?id=${id}&open=true`}>View deployment</Link>
2✔
85
  }
86
];
87

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

137
const deploymentStates = {
8✔
138
  any: { key: 'any', title: () => 'any', values: [] },
2✔
139
  pending: { key: 'pending', title: () => 'pending', values: deploymentStatesToSubstates.pending },
2✔
140
  inprogress: { key: 'inprogress', title: () => 'in progress', values: deploymentStatesToSubstates.inprogress },
2✔
141
  paused: { key: 'paused', title: () => 'paused', values: deploymentStatesToSubstates.paused },
2✔
142
  failures: { key: 'failures', title: () => 'failures', values: deploymentStatesToSubstates.failures },
2✔
143
  successes: { key: 'successes', title: () => 'successes', values: deploymentStatesToSubstates.successes }
2✔
144
};
145

146
export const Deployments = ({ device }) => {
8✔
147
  const [filters, setFilters] = useState([deploymentStates.any.key]);
2✔
148
  const [page, setPage] = useState(DEVICE_LIST_DEFAULTS.page);
2✔
149
  const [perPage, setPerPage] = useState(10);
2✔
150
  const [isChecking, setIsChecking] = useState(false);
2✔
151
  const { classes } = useStyles();
2✔
152
  const dispatch = useDispatch();
2✔
153

154
  useEffect(() => {
2✔
155
    if (!device?.id) {
2!
UNCOV
156
      return;
×
157
    }
158
    const filterSelection = deploymentStates[filters[0]].values;
2✔
159
    dispatch(getDeviceDeployments(device.id, { filterSelection, page, perPage }));
2✔
160
  }, [device.id, dispatch, filters, page, perPage]);
161

162
  const onSelectStatus = status => setFilters([status]);
2✔
163

164
  const onResetStart = () => setIsChecking(true);
2✔
165

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

168
  const { deviceDeployments = [], deploymentsCount } = device;
2!
169

170
  return (
2✔
171
    <div className="margin-bottom">
172
      <h4 className="margin-bottom-small">Deployments</h4>
173
      <div className="flexbox margin-bottom-small" style={{ alignSelf: 'flex-start' }}>
174
        <DeviceStateSelection onStateChange={onSelectStatus} selectedState={filters[0]} states={deploymentStates} />
175
      </div>
176

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

210
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