• 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

75.0
/src/js/components/auditlogs/auditlogslist.js
1
// Copyright 2020 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 from 'react';
15
import { Link } from 'react-router-dom';
16

17
import { ArrowRightAlt as ArrowRightAltIcon } from '@mui/icons-material';
18

19
import { SORTING_OPTIONS, canAccess } from '../../constants/appConstants';
20
import { DEPLOYMENT_ROUTES } from '../../constants/deploymentConstants';
21
import DeviceIdentityDisplay from '../common/deviceidentity';
22
import Loader from '../common/loader';
23
import Pagination from '../common/pagination';
24
import SortIcon from '../common/sorticon';
25
import Time from '../common/time';
26
import EventDetailsDrawer from './eventdetailsdrawer';
27

28
export const defaultRowsPerPage = 20;
4✔
29

30
const ArtifactLink = ({ item }) => <Link to={`/releases/${item.object.artifact.name}`}>View artifact</Link>;
4✔
31
const DeploymentLink = ({ item }) => <Link to={`${DEPLOYMENT_ROUTES.finished.route}?open=true&id=${item.object.id}`}>View deployment</Link>;
11✔
32
const DeviceLink = ({ item }) => <Link to={`/devices?id=${item.object.id}`}>View device</Link>;
4✔
33
const DeviceRejectedLink = ({ item }) => <Link to={`/devices/rejected?id=${item.object.id}`}>View device</Link>;
4✔
34
const TerminalSessionLink = () => <a>View session log</a>;
11✔
35
const UserChange = ({ item: { change = '-' } }) => {
4!
36
  const formatChange = change => {
11✔
37
    const diff = change.split(/@@.*@@/);
11✔
38
    return diff.length > 1 ? diff[1].trim() : diff;
11!
39
  };
40
  return (
11✔
41
    <div className="capitalized" style={{ alignItems: 'flex-start', whiteSpace: 'pre-line' }}>
42
      {formatChange(change)}
43
    </div>
44
  );
45
};
46

47
const FallbackFormatter = props => {
4✔
UNCOV
48
  let result = '';
×
UNCOV
49
  try {
×
UNCOV
50
    result = JSON.stringify(props);
×
51
  } catch (error) {
UNCOV
52
    console.log(error);
×
53
  }
UNCOV
54
  return <div>{result}</div>;
×
55
};
56

57
const ArtifactFormatter = ({ artifact }) => <div>{artifact.name}</div>;
4✔
58
const DeploymentFormatter = ({ deployment }) => <div>{deployment.name}</div>;
11✔
59
const DeviceFormatter = ({ id }) => <DeviceIdentityDisplay device={{ id }} />;
11✔
60
const UserFormatter = ({ user }) => <div>{user.email}</div>;
11✔
61

62
const defaultAccess = canAccess;
4✔
63
const changeMap = {
4✔
64
  default: { component: 'div', actionFormatter: FallbackFormatter, title: 'defaultTitle', accessCheck: defaultAccess },
UNCOV
65
  artifact: { actionFormatter: ArtifactFormatter, component: ArtifactLink, accessCheck: ({ canReadReleases }) => canReadReleases },
×
66
  deployment: {
67
    actionFormatter: DeploymentFormatter,
68
    component: DeploymentLink,
69
    accessCheck: ({ canReadDeployments }) => canReadDeployments
11✔
70
  },
71
  deviceDecommissioned: { actionFormatter: DeviceFormatter, component: 'div', accessCheck: defaultAccess },
UNCOV
72
  deviceRejected: { actionFormatter: DeviceFormatter, component: DeviceRejectedLink, accessCheck: ({ canReadDevices }) => canReadDevices },
×
UNCOV
73
  deviceGeneral: { actionFormatter: DeviceFormatter, component: DeviceLink, accessCheck: ({ canReadDevices }) => canReadDevices },
×
74
  deviceTerminalSession: { actionFormatter: DeviceFormatter, component: TerminalSessionLink, accessCheck: defaultAccess },
75
  user: { component: UserChange, actionFormatter: UserFormatter, accessCheck: defaultAccess }
76
};
77

78
const mapChangeToContent = item => {
4✔
79
  let content = changeMap[item.object.type];
66✔
80
  if (content) {
66✔
81
    return content;
44✔
82
  } else if (item.object.type === 'device' && item.action.includes('terminal')) {
22!
83
    content = changeMap.deviceTerminalSession;
22✔
UNCOV
84
  } else if (item.object.type === 'device' && item.action.includes('reject')) {
×
UNCOV
85
    content = changeMap.deviceRejected;
×
UNCOV
86
  } else if (item.object.type === 'device' && item.action.includes('decommission')) {
×
UNCOV
87
    content = changeMap.deviceDecommissioned;
×
UNCOV
88
  } else if (item.object.type === 'device') {
×
UNCOV
89
    content = changeMap.deviceGeneral;
×
90
  } else {
UNCOV
91
    content = changeMap.default;
×
92
  }
93
  return content;
22✔
94
};
95

96
const actorMap = {
4✔
97
  user: 'email',
98
  device: 'id'
99
};
100

101
const UserDescriptor = (item, index) => <div key={`${item.time}-${index} `}>{item.actor[actorMap[item.actor.type]]}</div>;
33✔
102
const ActionDescriptor = (item, index) => (
4✔
103
  <div className="uppercased" key={`${item.time}-${index}`}>
33✔
104
    {item.action}
105
  </div>
106
);
107
const TypeDescriptor = (item, index) => (
4✔
108
  <div className="capitalized" key={`${item.time}-${index}`}>
33✔
109
    {item.object.type}
110
  </div>
111
);
112
const ChangeDescriptor = (item, index) => {
4✔
113
  const FormatterComponent = mapChangeToContent(item).actionFormatter;
33✔
114
  return <FormatterComponent key={`${item.time}-${index}`} {...item.object} />;
33✔
115
};
116
const ChangeDetailsDescriptor = (item, index, userCapabilities) => {
4✔
117
  const { component: Comp, accessCheck } = mapChangeToContent(item);
33✔
118
  const key = `${item.time}-${index}`;
33✔
119
  return accessCheck(userCapabilities) ? <Comp key={key} item={item} /> : <div key={key} />;
33!
120
};
121
const TimeWrapper = (item, index) => <Time key={`${item.time}-${index}`} value={item.time} />;
33✔
122

123
const auditLogColumns = [
4✔
124
  { title: 'User', sortable: false, render: UserDescriptor },
125
  { title: 'Action', sortable: false, render: ActionDescriptor },
126
  { title: 'Type', sortable: false, render: TypeDescriptor },
127
  { title: 'Changed', sortable: false, render: ChangeDescriptor },
128
  { title: 'More details', sortable: false, render: ChangeDetailsDescriptor },
129
  { title: 'Time', sortable: true, render: TimeWrapper }
130
];
131

132
export const AuditLogsList = ({
4✔
133
  eventItem,
134
  items,
135
  loading,
136
  onChangePage,
137
  onChangeRowsPerPage,
138
  onChangeSorting,
139
  selectionState,
140
  setAuditlogsState,
141
  userCapabilities
142
}) => {
143
  const { page, perPage, sort = {}, total: count } = selectionState;
11!
144

145
  const onIssueSelection = selectedIssue =>
11✔
146
    setAuditlogsState({ selectedId: selectedIssue ? btoa(`${selectedIssue.action}|${selectedIssue.time}`) : undefined });
1!
147

148
  return (
11✔
149
    !!items.length && (
22✔
150
      <div className="fadeIn deploy-table-contain auditlogs-list">
151
        <div className="auditlogs-list-item auditlogs-list-item-header muted">
152
          {auditLogColumns.map((column, index) => (
153
            <div
66✔
154
              className="columnHeader"
155
              key={`columnHeader-${index}`}
UNCOV
156
              onClick={() => (column.sortable ? onChangeSorting() : null)}
×
157
              style={column.sortable ? {} : { cursor: 'initial' }}
66✔
158
            >
159
              {column.title}
160
              {column.sortable && <SortIcon columnKey="time" sortDown={sort.direction === SORTING_OPTIONS.desc} />}
77✔
161
            </div>
162
          ))}
163
          <div />
164
        </div>
165
        <div className="auditlogs-list">
166
          {items.map(item => {
167
            const allowsExpansion = !!item.change || item.action.includes('terminal') || item.action.includes('portforward');
33!
168
            return (
33✔
169
              <div
170
                className={`auditlogs-list-item ${allowsExpansion ? 'clickable' : ''}`}
33!
171
                key={`event-${item.time}`}
172
                onClick={() => onIssueSelection(allowsExpansion ? item : undefined)}
1!
173
              >
174
                {auditLogColumns.map((column, index) => column.render(item, index, userCapabilities))}
198✔
175
                {allowsExpansion ? (
33!
176
                  <div className="uppercased link-color bold">
177
                    view details <ArrowRightAltIcon />
178
                  </div>
179
                ) : (
180
                  <div />
181
                )}
182
              </div>
183
            );
184
          })}
185
        </div>
186
        <div className="flexbox margin-top">
187
          <Pagination
188
            className="margin-top-none"
189
            count={count}
190
            rowsPerPage={perPage}
191
            onChangeRowsPerPage={onChangeRowsPerPage}
192
            page={page}
193
            onChangePage={onChangePage}
194
          />
195
          <Loader show={loading} small />
196
        </div>
UNCOV
197
        <EventDetailsDrawer eventItem={eventItem} open={Boolean(eventItem)} onClose={() => onIssueSelection()} />
×
198
      </div>
199
    )
200
  );
201
};
202

203
export default AuditLogsList;
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