• 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

74.47
/src/js/components/releases/releaseslist.js
1
// Copyright 2019 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, { useCallback, useMemo, useRef } from 'react';
15
import Dropzone from 'react-dropzone';
16
import { useDispatch, useSelector } from 'react-redux';
17

18
import { makeStyles } from 'tss-react/mui';
19

20
import { setSnackbar } from '../../actions/appActions';
21
import { selectRelease, setReleasesListState } from '../../actions/releaseActions';
22
import { SORTING_OPTIONS, canAccess as canShow } from '../../constants/appConstants';
23
import { DEVICE_LIST_DEFAULTS } from '../../constants/deviceConstants';
24
import { getFeatures, getHasReleases, getReleaseListState, getReleasesList, getUserCapabilities } from '../../selectors';
25
import DetailsTable from '../common/detailstable';
26
import Loader from '../common/loader';
27
import Pagination from '../common/pagination';
28
import { RelativeTime } from '../common/time';
29

30
const columns = [
4✔
31
  {
32
    key: 'name',
33
    title: 'Name',
34
    render: ({ name }) => name,
270✔
35
    sortable: true,
36
    defaultSortDirection: SORTING_OPTIONS.asc,
37
    canShow
38
  },
39
  {
40
    key: 'artifacts-count',
41
    title: 'Number of artifacts',
42
    render: ({ artifacts = [] }) => artifacts.length,
270!
43
    canShow
44
  },
45
  {
46
    key: 'tags',
47
    title: 'Tags',
48
    render: ({ tags = [] }) => tags.join(', ') || '-',
270✔
49
    canShow
50
  },
51
  {
52
    key: 'modified',
53
    title: 'Last modified',
54
    render: ({ modified }) => <RelativeTime updateTime={modified} />,
270✔
55
    defaultSortDirection: SORTING_OPTIONS.desc,
56
    sortable: true,
57
    canShow
58
  }
59
];
60

61
const useStyles = makeStyles()(() => ({
5✔
62
  empty: { margin: '8vh auto' }
63
}));
64

65
const { page: defaultPage, perPage: defaultPerPage } = DEVICE_LIST_DEFAULTS;
4✔
66

67
const EmptyState = ({ canUpload, className = '', dropzoneRef, uploading, onDrop, onUpload }) => (
4!
68
  <div className={`dashboard-placeholder fadeIn ${className}`} ref={dropzoneRef}>
×
69
    <Dropzone activeClassName="active" disabled={uploading} multiple={false} noClick={true} onDrop={onDrop} rejectClassName="active">
70
      {({ getRootProps, getInputProps }) => (
71
        <div {...getRootProps({ className: uploading ? 'dropzone disabled muted' : 'dropzone' })} onClick={() => onUpload()}>
×
72
          <input {...getInputProps()} disabled={uploading} />
73
          <p>
74
            There are no Releases yet.{' '}
75
            {canUpload && (
×
76
              <>
77
                <a>Upload an Artifact</a> to create a new Release
78
              </>
79
            )}
80
          </p>
81
        </div>
82
      )}
83
    </Dropzone>
84
  </div>
85
);
86

87
export const ReleasesList = ({ className = '', onFileUploadClick }) => {
4✔
88
  const repoRef = useRef();
43✔
89
  const dropzoneRef = useRef();
43✔
90
  const uploading = useSelector(state => state.app.uploading);
81✔
91
  const releasesListState = useSelector(getReleaseListState);
43✔
92
  const { isLoading, page = defaultPage, perPage = defaultPerPage, searchTerm, sort = {}, searchTotal, selectedTags = [], total, type } = releasesListState;
43!
93
  const hasReleases = useSelector(getHasReleases);
43✔
94
  const features = useSelector(getFeatures);
43✔
95
  const releases = useSelector(getReleasesList);
43✔
96
  const userCapabilities = useSelector(getUserCapabilities);
43✔
97
  const dispatch = useDispatch();
43✔
98
  const { classes } = useStyles();
43✔
99

100
  const { canUploadReleases } = userCapabilities;
43✔
101
  const { key: attribute, direction } = sort;
43✔
102

103
  const onSelect = useCallback(id => dispatch(selectRelease(id)), [dispatch]);
43✔
104

105
  const onChangeSorting = sortKey => {
43✔
106
    let sort = { key: sortKey, direction: direction === SORTING_OPTIONS.asc ? SORTING_OPTIONS.desc : SORTING_OPTIONS.asc };
×
107
    if (sortKey !== attribute) {
×
108
      sort = { ...sort, direction: columns.find(({ key }) => key === sortKey)?.defaultSortDirection ?? SORTING_OPTIONS.desc };
×
109
    }
110
    dispatch(setReleasesListState({ page: 1, sort }));
×
111
  };
112

113
  const onChangePagination = (page, currentPerPage = perPage) => dispatch(setReleasesListState({ page, perPage: currentPerPage }));
43!
114

115
  const onDrop = (acceptedFiles, rejectedFiles) => {
43✔
116
    if (acceptedFiles.length) {
×
117
      onFileUploadClick(acceptedFiles[0]);
×
118
    }
119
    if (rejectedFiles.length) {
×
120
      dispatch(setSnackbar(`File '${rejectedFiles[0].name}' was rejected. File should be of type .mender`, null));
×
121
    }
122
  };
123

124
  const applicableColumns = useMemo(
43✔
125
    () =>
126
      columns.reduce((accu, column) => {
5✔
127
        if (column.canShow({ features })) {
20!
128
          accu.push(column);
20✔
129
        }
130
        return accu;
20✔
131
      }, []),
132
    // eslint-disable-next-line react-hooks/exhaustive-deps
133
    [JSON.stringify(features)]
134
  );
135

136
  const isFiltering = !!(selectedTags.length || type || searchTerm);
43✔
137
  const potentialTotal = isFiltering ? searchTotal : total;
43✔
138
  if (!hasReleases) {
43!
139
    return (
×
140
      <EmptyState
141
        canUpload={canUploadReleases}
142
        className={classes.empty}
143
        dropzoneRef={dropzoneRef}
144
        uploading={uploading}
145
        onDrop={onDrop}
146
        onUpload={onFileUploadClick}
147
      />
148
    );
149
  }
150

151
  return (
43✔
152
    <div className={className}>
153
      {isLoading === undefined ? (
43!
154
        <Loader show />
155
      ) : !potentialTotal ? (
43✔
156
        <p className="margin-top muted align-center margin-right">There are no Releases {isFiltering ? 'for the filter selection' : 'yet'}</p>
1!
157
      ) : (
158
        <>
159
          <DetailsTable columns={applicableColumns} items={releases} onItemClick={onSelect} sort={sort} onChangeSorting={onChangeSorting} tableRef={repoRef} />
160
          <div className="flexbox">
161
            <Pagination
162
              className="margin-top-none"
163
              count={potentialTotal}
164
              rowsPerPage={perPage}
165
              onChangePage={onChangePagination}
166
              onChangeRowsPerPage={newPerPage => onChangePagination(1, newPerPage)}
×
167
              page={page}
168
            />
169
            <Loader show={isLoading} small />
170
          </div>
171
        </>
172
      )}
173
    </div>
174
  );
175
};
176

177
export default ReleasesList;
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