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

mendersoftware / gui / 901187442

pending completion
901187442

Pull #3795

gitlab-ci

mzedel
feat: increased chances of adopting our intended navigation patterns instead of unsupported browser navigation

Ticket: None
Changelog: None
Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #3795: feat: increased chances of adopting our intended navigation patterns instead of unsupported browser navigation

4389 of 6365 branches covered (68.96%)

5 of 5 new or added lines in 1 file covered. (100.0%)

1729 existing lines in 165 files now uncovered.

8274 of 10019 relevant lines covered (82.58%)

144.86 hits per line

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

75.76
/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 { Link } from 'react-router-dom';
16

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

20
import { deploymentsApiUrl } from '../../../actions/deploymentActions';
21
import { deploymentDisplayStates, deploymentStatesToSubstates } from '../../../constants/deploymentConstants';
22
import { DEVICE_LIST_DEFAULTS } from '../../../constants/deviceConstants';
23
import Confirm from '../../common/confirm';
24
import InfoHint from '../../common/info-hint';
25
import Pagination from '../../common/pagination';
26
import { MaybeTime } from '../../common/time';
27
import { DeviceStateSelection } from '../authorized-devices';
28

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

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

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

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

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

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

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

150
  const onSelectStatus = status => setFilters([status]);
2✔
151

152
  const onResetStart = () => setIsChecking(true);
2✔
153

154
  const onResetConfirm = () => resetDeviceDeployments(device.id).then(() => setIsChecking(false));
2✔
155

156
  const { deviceDeployments = [], deploymentsCount } = device;
2!
157

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

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

198
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