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

mendersoftware / gui / 1081664682

22 Nov 2023 02:11PM UTC coverage: 82.798% (-17.2%) from 99.964%
1081664682

Pull #4214

gitlab-ci

tranchitella
fix: Fixed the infinite page redirects when the back button is pressed

Remove the location and navigate from the useLocationParams.setValue callback
dependencies as they change the set function that is presented in other
useEffect dependencies. This happens when the back button is clicked, which
leads to the location changing infinitely.

Changelog: Title
Ticket: MEN-6847
Ticket: MEN-6796

Signed-off-by: Ihor Aleksandrychiev <ihor.aleksandrychiev@northern.tech>
Signed-off-by: Fabio Tranchitella <fabio.tranchitella@northern.tech>
Pull Request #4214: fix: Fixed the infinite page redirects when the back button is pressed

4319 of 6292 branches covered (0.0%)

8332 of 10063 relevant lines covered (82.8%)

191.0 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