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

mendersoftware / gui / 1113439055

19 Dec 2023 09:01PM UTC coverage: 82.752% (-17.2%) from 99.964%
1113439055

Pull #4258

gitlab-ci

mender-test-bot
chore: Types update

Signed-off-by: Mender Test Bot <mender@northern.tech>
Pull Request #4258: chore: Types update

4326 of 6319 branches covered (0.0%)

8348 of 10088 relevant lines covered (82.75%)

189.39 hits per line

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

76.71
/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, { useMemo } from 'react';
15
import { Link } from 'react-router-dom';
16

17
import { ArrowRightAlt as ArrowRightAltIcon, Sort as SortIcon } 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 Time from '../common/time';
25
import EventDetailsDrawer from './eventdetailsdrawer';
26

27
export const defaultRowsPerPage = 20;
4✔
28

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

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

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

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

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

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

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

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

131
export const AuditLogsList = ({ items, loading, onChangePage, onChangeRowsPerPage, onChangeSorting, selectionState, setAuditlogsState, userCapabilities }) => {
4✔
132
  const { page, perPage, selectedId, sort = {}, total: count } = selectionState;
19!
133

134
  const onIssueSelection = selectedIssue =>
19✔
135
    setAuditlogsState({ selectedId: selectedIssue ? btoa(`${selectedIssue.action}|${selectedIssue.time}`) : undefined });
1!
136

137
  const eventItem = useMemo(() => {
19✔
138
    if (!selectedId) {
8✔
139
      return;
7✔
140
    }
141
    const [eventAction, eventTime] = atob(selectedId).split('|');
1✔
142
    return items.find(item => item.action === eventAction && item.time === eventTime);
3✔
143
  }, [items, selectedId]);
144

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

200
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