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

mendersoftware / gui / 1297412022

20 May 2024 10:08AM UTC coverage: 83.401%. First build
1297412022

Pull #4418

gitlab-ci

web-flow
Merge pull request #4399 from tranchitella/men-6823

fix: use the two-steps log in on hosted
Pull Request #4418: Sync staging with master

4463 of 6369 branches covered (70.07%)

1 of 2 new or added lines in 2 files covered. (50.0%)

8466 of 10151 relevant lines covered (83.4%)

140.65 hits per line

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

86.0
/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 { deploymentDisplayStates, 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✔
NEW
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
  {
61
    content: 'Target device(s)',
62
    key: 'target',
63
    Component: ({ deviceDeployment: { id, target, route } }) => <Link to={`/deployments/${route}?id=${id}&open=true`}>{target}</Link>
2✔
64
  },
65
  { content: 'Started', key: 'created', Component: ({ deviceDeployment: { created } }) => <MaybeTime value={created} /> },
2✔
66
  { content: 'Finished', key: 'finished', Component: ({ deviceDeployment: { finished } }) => <MaybeTime value={finished} /> },
2✔
67
  {
68
    content: 'Deployment progress',
69
    key: 'deploymentStatus',
70
    Component: ({ deviceDeployment: { deploymentStatus } }) => deploymentDisplayStates[deploymentStatus] ?? deploymentStatus
2!
71
  },
72
  { content: 'Device status', key: 'status', Component: ({ deviceDeployment: { status } }) => status },
2✔
73
  {
74
    content: '',
75
    key: 'log',
76
    Component: ({ deviceDeployment: { id, log, deviceId }, token }) =>
77
      log && (
2✔
78
        <Button
79
          onClick={() =>
80
            createDownload(
×
81
              ` ${window.location.origin}${deploymentsApiUrl}/deployments/${id}/devices/${deviceId}/log`,
82
              `device_${deviceId}_deployment_${id}.log`,
83
              token
84
            )
85
          }
86
        >
87
          Log
88
        </Button>
89
      )
90
  }
91
];
92

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

142
const deploymentStates = {
8✔
143
  any: { key: 'any', title: () => 'any', values: [] },
2✔
144
  pending: { key: 'pending', title: () => 'pending', values: deploymentStatesToSubstates.pending },
2✔
145
  inprogress: { key: 'inprogress', title: () => 'in progress', values: deploymentStatesToSubstates.inprogress },
2✔
146
  paused: { key: 'paused', title: () => 'paused', values: deploymentStatesToSubstates.paused },
2✔
147
  failures: { key: 'failures', title: () => 'failures', values: deploymentStatesToSubstates.failures },
2✔
148
  successes: { key: 'successes', title: () => 'successes', values: deploymentStatesToSubstates.successes }
2✔
149
};
150

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

159
  useEffect(() => {
2✔
160
    if (!device?.id) {
2!
161
      return;
×
162
    }
163
    const filterSelection = deploymentStates[filters[0]].values;
2✔
164
    dispatch(getDeviceDeployments(device.id, { filterSelection, page, perPage }));
2✔
165
  }, [device.id, dispatch, filters, page, perPage]);
166

167
  const onSelectStatus = status => setFilters([status]);
2✔
168

169
  const onResetStart = () => setIsChecking(true);
2✔
170

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

173
  const { deviceDeployments = [], deploymentsCount } = device;
2!
174

175
  return (
2✔
176
    <div className="margin-bottom">
177
      <h4 className="margin-bottom-small">Deployments</h4>
178
      <div className="flexbox margin-bottom-small" style={{ alignSelf: 'flex-start' }}>
179
        <DeviceStateSelection onStateChange={onSelectStatus} selectedState={filters[0]} states={deploymentStates} />
180
      </div>
181

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

215
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