• 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

67.44
/src/js/components/devices/troubleshoot/filetransfer.js
1
// Copyright 2021 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, useEffect, useState } from 'react';
15
import { useDispatch } from 'react-redux';
16

17
import { FileCopy as CopyPasteIcon } from '@mui/icons-material';
18
import { Button, Divider, IconButton, InputAdornment, Tab, Tabs, TextField, Tooltip } from '@mui/material';
19
import { makeStyles } from 'tss-react/mui';
20

21
import { deviceFileUpload } from '../../../actions/deviceActions';
22
import { canAccess } from '../../../constants/appConstants';
23
import FileUpload from '../../common/forms/fileupload';
24

25
const tabs = [
11✔
26
  { key: 'upload', canAccess: ({ userCapabilities: { canTroubleshoot, canWriteDevices } }) => canTroubleshoot && canWriteDevices },
4✔
27
  { key: 'download', canAccess }
28
];
29

30
const useStyles = makeStyles()(theme => ({
11✔
31
  column: { maxWidth: theme.spacing(80) },
32
  inputWrapper: { alignItems: 'end', gap: theme.spacing(2), display: 'grid', gridTemplateColumns: `${theme.spacing(60)} min-content` }
33
}));
34

35
const CopyPasteButton = ({ onClick }) => (
11✔
36
  <InputAdornment position="end">
6✔
37
    <Tooltip title="Paste" placement="top">
38
      <IconButton onClick={onClick}>
39
        <CopyPasteIcon />
40
      </IconButton>
41
    </Tooltip>
42
  </InputAdornment>
43
);
44

45
export const FileTransfer = ({
11✔
46
  device: { id: deviceId },
47
  downloadPath,
48
  file,
49
  onDownload,
50
  setFile,
51
  setDownloadPath,
52
  setUploadPath,
53
  uploadPath,
54
  userCapabilities
55
}) => {
56
  const { classes } = useStyles();
6✔
57
  const [currentTab, setCurrentTab] = useState(tabs[0].key);
6✔
58
  const [isValidDestination, setIsValidDestination] = useState(true);
6✔
59
  const [availableTabs, setAvailableTabs] = useState(tabs);
6✔
60
  const dispatch = useDispatch();
6✔
61

62
  useEffect(() => {
6✔
63
    let destination = currentTab === 'download' ? downloadPath : uploadPath;
4!
64
    const isValid = destination.length ? /^(?:\/|[a-z]+:\/\/)/.test(destination) : true;
4!
65
    setIsValidDestination(isValid);
4✔
66
  }, [currentTab, downloadPath, uploadPath]);
67

68
  useEffect(() => {
6✔
69
    const availableTabs = tabs.reduce((accu, item) => {
4✔
70
      if (item.canAccess({ userCapabilities })) {
8!
71
        accu.push(item);
8✔
72
      }
73
      return accu;
8✔
74
    }, []);
75
    setAvailableTabs(availableTabs);
4✔
76
    setCurrentTab(availableTabs[0].key);
4✔
77
    // eslint-disable-next-line react-hooks/exhaustive-deps
78
  }, [JSON.stringify(userCapabilities)]);
79

80
  const onPasteDownloadClick = async () => {
6✔
81
    const path = await navigator.clipboard.readText();
×
82
    setDownloadPath(path);
×
83
  };
84

85
  const onPasteUploadClick = async () => {
6✔
86
    const path = await navigator.clipboard.readText();
×
87
    setUploadPath(path);
×
88
  };
89

90
  const onFileSelect = selectedFile => {
6✔
91
    let path;
92
    if (selectedFile) {
×
93
      path = `${uploadPath}/${selectedFile.name}`;
×
94
    } else {
95
      path = file && uploadPath.includes(file.name) ? uploadPath.substring(0, uploadPath.lastIndexOf('/')) : uploadPath;
×
96
    }
97
    setUploadPath(path);
×
98
    setFile(selectedFile);
×
99
  };
100

101
  const onUploadClick = useCallback(() => dispatch(deviceFileUpload(deviceId, uploadPath, file)), [dispatch, deviceId, uploadPath, file]);
6✔
102

103
  const fileInputProps = {
6✔
104
    error: !isValidDestination,
105
    fullWidth: true,
106
    InputLabelProps: { shrink: true }
107
  };
108

109
  return (
6✔
110
    <div className={classes.column}>
111
      <Tabs onChange={(e, item) => setCurrentTab(item)} value={currentTab} visibleScrollbar>
×
112
        {availableTabs.map(({ key }) => (
113
          <Tab className="capitalized" key={key} label={key} value={key} />
12✔
114
        ))}
115
      </Tabs>
116
      <Divider />
117
      {currentTab === 'upload' ? (
6!
118
        <div className={classes.column}>
119
          <div>
120
            <p className="margin-top">
121
              <b>Upload a file to the device</b>
122
            </p>
123
            <FileUpload
124
              enableContentReading={false}
125
              fileNameSelection={file?.name}
126
              onFileChange={() => undefined}
×
127
              onFileSelect={onFileSelect}
128
              placeholder={
129
                <>
130
                  Drag here or <a>browse</a> to upload a file
131
                </>
132
              }
133
            />
134
          </div>
135
          <p className="margin-top margin-bottom-none">
136
            <b>Destination directory on the device where the file will be transferred</b>
137
          </p>
138
          <div className={classes.inputWrapper}>
139
            <TextField
140
              {...fileInputProps}
141
              helperText={!isValidDestination && <div className="warning">Destination has to be an absolute path</div>}
6!
142
              onChange={e => setUploadPath(e.target.value)}
×
143
              placeholder="Example: /opt/installed-by-single-file"
144
              value={uploadPath}
145
              InputProps={{ endAdornment: <CopyPasteButton onClick={onPasteUploadClick} /> }}
146
            />
147
            <Button variant="contained" color="primary" disabled={!(file && uploadPath && isValidDestination)} onClick={onUploadClick}>
6!
148
              Upload
149
            </Button>
150
          </div>
151
        </div>
152
      ) : (
153
        <div>
154
          <p className="margin-top margin-bottom-none">
155
            <b>Path to the file on the device</b>
156
          </p>
157
          <div className={classes.inputWrapper}>
158
            <TextField
159
              {...fileInputProps}
160
              helperText={!isValidDestination && <div className="warning">Destination has to be an absolute path</div>}
×
161
              onChange={e => setDownloadPath(e.target.value)}
×
162
              placeholder="Example: /home/mender/"
163
              value={downloadPath}
164
              InputProps={{ endAdornment: <CopyPasteButton onClick={onPasteDownloadClick} /> }}
165
            />
166
            <Button
167
              variant="contained"
168
              color="primary"
169
              disabled={!(downloadPath && isValidDestination)}
×
170
              onClick={() => onDownload(downloadPath)}
×
171
              style={{ alignSelf: 'flex-end' }}
172
            >
173
              Download
174
            </Button>
175
          </div>
176
        </div>
177
      )}
178
    </div>
179
  );
180
};
181

182
export default FileTransfer;
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