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

mendersoftware / gui / 951400782

pending completion
951400782

Pull #3900

gitlab-ci

web-flow
chore: bump @testing-library/jest-dom from 5.16.5 to 5.17.0

Bumps [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) from 5.16.5 to 5.17.0.
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/jest-dom/compare/v5.16.5...v5.17.0)

---
updated-dependencies:
- dependency-name: "@testing-library/jest-dom"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #3900: chore: bump @testing-library/jest-dom from 5.16.5 to 5.17.0

4446 of 6414 branches covered (69.32%)

8342 of 10084 relevant lines covered (82.73%)

186.0 hits per line

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

82.48
/src/js/components/releases/dialogs/addartifact.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, { useCallback, useEffect, useRef, useState } from 'react';
15
import Dropzone from 'react-dropzone';
16

17
import { CloudUpload, Delete as DeleteIcon, InsertDriveFile as InsertDriveFileIcon } from '@mui/icons-material';
18
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Divider, IconButton } from '@mui/material';
19
import { makeStyles } from 'tss-react/mui';
20

21
import { onboardingSteps } from '../../../constants/onboardingConstants';
22
import { FileSize, unionizeStrings } from '../../../helpers';
23
import Tracking from '../../../tracking';
24
import { getOnboardingComponentFor } from '../../../utils/onboardingmanager';
25
import useWindowSize from '../../../utils/resizehook';
26
import InfoHint from '../../common/info-hint';
27
import ArtifactInformationForm from './artifactinformationform';
28
import ArtifactUploadConfirmation from './artifactupload';
29

30
const reFilename = new RegExp(/^[a-z0-9.,_-]+$/i);
7✔
31

32
const useStyles = makeStyles()(theme => ({
7✔
33
  dropzone: { ['&.dropzone']: { padding: theme.spacing(4) } },
34
  fileInfo: {
35
    alignItems: 'center',
36
    columnGap: theme.spacing(4),
37
    display: 'grid',
38
    gridTemplateColumns: 'max-content 1fr max-content',
39
    marginBottom: theme.spacing(2),
40
    marginRight: theme.spacing(4)
41
  },
42
  fileSizeWrapper: { marginTop: 5 },
43
  infoIcon: { '> svg': { alignSelf: 'flex-start', marginTop: theme.spacing(0.5) } }
44
}));
45

46
const uploadTypes = {
7✔
47
  mender: {
48
    key: 'mender',
49
    component: ArtifactUploadConfirmation
50
  },
51
  singleFile: {
52
    key: 'singleFile',
53
    component: ArtifactInformationForm
54
  }
55
};
56

57
const fileInformationContent = {
7✔
58
  mender: {
59
    title: 'Mender Artifact',
60
    icon: InsertDriveFileIcon,
61
    info: (
62
      <>
63
        If there is no Release matching this Artifact’s name, a new Release will be created for this Artifact.
64
        <br />
65
        <br />
66
        If there is already a Release matching this Artifact’s name, the Artifact will be grouped in that Release.
67
      </>
68
    )
69
  },
70
  singleFile: {
71
    title: 'Single File',
72
    icon: InsertDriveFileIcon,
73
    info: `This will generate a single file application update Artifact, which requires some additional metadata to be entered.`
74
  }
75
};
76

77
export const FileInformation = ({ file, type, onRemove }) => {
7✔
78
  if (!file) {
41✔
79
    return <div />;
1✔
80
  }
81
  const { classes } = useStyles();
40✔
82
  const { icon: Icon, info, title } = fileInformationContent[type];
40✔
83
  return (
40✔
84
    <>
85
      <h4>Selected {title}</h4>
86
      <div className={classes.fileInfo}>
87
        <Icon size="large" />
88
        <div className="flexbox column">
89
          <div>{file.name}</div>
90
          <div className={`muted ${classes.fileSizeWrapper}`}>
91
            <FileSize fileSize={file.size} />
92
          </div>
93
        </div>
94
        <IconButton size="large" onClick={onRemove}>
95
          <DeleteIcon />
96
        </IconButton>
97
      </div>
98
      <Divider className="margin-right-large" />
99
      <InfoHint className={classes.infoIcon} content={info} />
100
    </>
101
  );
102
};
103

104
const commonExtensions = ['zip', 'txt', 'tar', 'html', 'tar.gzip', 'gzip'];
7✔
105
const shortenFileName = name => {
7✔
106
  const extension = commonExtensions.find(extension => name.endsWith(extension));
2✔
107
  if (extension) {
1!
108
    const dotIndex = name.lastIndexOf(`.${extension}`);
1✔
109
    return name.substring(0, dotIndex);
1✔
110
  }
111
  return name;
×
112
};
113

114
export const ArtifactUpload = ({ advanceOnboarding, onboardingState, releases, setSnackbar, updateCreation }) => {
7✔
115
  const onboardingAnchor = useRef();
3✔
116
  const { classes } = useStyles();
3✔
117
  // eslint-disable-next-line no-unused-vars
118
  const size = useWindowSize();
3✔
119

120
  const onDrop = acceptedFiles => {
3✔
121
    const emptyFileInfo = { file: undefined, name: '', type: uploadTypes.mender.key };
2✔
122
    if (acceptedFiles.length === 1) {
2!
123
      if (!reFilename.test(acceptedFiles[0].name)) {
2!
124
        updateCreation(emptyFileInfo);
×
125
        setSnackbar('Only letters, digits and characters in the set ".,_-" are allowed in the filename.', null);
×
126
      } else {
127
        if (releases.length && !onboardingState.complete) {
2✔
128
          advanceOnboarding(onboardingSteps.UPLOAD_NEW_ARTIFACT_DIALOG_UPLOAD);
1✔
129
        }
130
        const { name } = acceptedFiles[0];
2✔
131
        updateCreation({
2✔
132
          file: acceptedFiles[0],
133
          name: onboardingState.complete ? shortenFileName(name) : '',
2✔
134
          type: name.endsWith('.mender') ? uploadTypes.mender.key : uploadTypes.singleFile.key
2✔
135
        });
136
      }
137
    } else {
138
      updateCreation(emptyFileInfo);
×
139
      setSnackbar('The selected file is not supported.', null);
×
140
    }
141
  };
142

143
  let onboardingComponent = null;
3✔
144
  if (!onboardingState.complete && onboardingAnchor.current) {
3!
145
    const anchor = {
×
146
      left: onboardingAnchor.current.offsetLeft + onboardingAnchor.current.clientWidth,
147
      top: onboardingAnchor.current.offsetTop + onboardingAnchor.current.clientHeight / 2
148
    };
149
    onboardingComponent = getOnboardingComponentFor(onboardingSteps.UPLOAD_NEW_ARTIFACT_DIALOG_UPLOAD, onboardingState, { anchor, place: 'right' });
×
150
  }
151
  return (
3✔
152
    <>
153
      <div className="flexbox column centered margin">
154
        Upload a premade Mender Artifact
155
        <p className="muted">OR</p>
156
        Upload a file to generate a single file application update Artifact
157
      </div>
158
      <Dropzone multiple={false} onDrop={onDrop}>
159
        {({ getRootProps, getInputProps }) => (
160
          <div {...getRootProps({ className: `fadeIn onboard dropzone ${classes.dropzone}` })} ref={onboardingAnchor}>
13✔
161
            <input {...getInputProps()} />
162
            <CloudUpload fontSize="large" className="muted" />
163
            <div>
164
              Drag and drop here or <b>browse</b> to upload
165
            </div>
166
          </div>
167
        )}
168
      </Dropzone>
169
      {!!onboardingComponent && onboardingComponent}
3!
170
    </>
171
  );
172
};
173

174
export const AddArtifactDialog = ({
7✔
175
  advanceOnboarding,
176
  createArtifact,
177
  deviceTypes = [],
2✔
178
  onboardingState,
179
  onCancel,
180
  onUploadStarted,
181
  pastCount,
182
  releases,
183
  selectedFile,
184
  setSnackbar,
185
  uploadArtifact
186
}) => {
187
  const [activeStep, setActiveStep] = useState(0);
45✔
188
  const [creation, setCreation] = useState({
45✔
189
    customDeviceTypes: '',
190
    destination: '',
191
    file: undefined,
192
    fileSystem: 'rootfs-image',
193
    finalStep: false,
194
    isValid: false,
195
    isValidDestination: false,
196
    name: '',
197
    selectedDeviceTypes: [],
198
    softwareName: '',
199
    softwareVersion: '',
200
    type: uploadTypes.mender.key
201
  });
202

203
  useEffect(() => {
45✔
204
    setCreation(current => ({ ...current, file: selectedFile }));
3✔
205
  }, [selectedFile]);
206

207
  const onUpload = useCallback(() => {
45✔
208
    const { customDeviceTypes, destination, file, fileSystem, name, selectedDeviceTypes, softwareName, softwareVersion } = creation;
2✔
209
    const { name: filename = '' } = file;
2!
210
    let meta = { description: '' };
2✔
211
    if (filename.endsWith('.mender')) {
2✔
212
      return addArtifact(meta, file, 'upload');
1✔
213
    }
214
    const otherDeviceTypes = customDeviceTypes.split(',');
1✔
215
    const deviceTypes = unionizeStrings(selectedDeviceTypes, otherDeviceTypes);
1✔
216
    meta = {
1✔
217
      ...meta,
218
      device_types_compatible: deviceTypes,
219
      args: { dest_dir: destination, filename, software_filesystem: fileSystem, software_name: softwareName, software_version: softwareVersion },
220
      name
221
    };
222
    return addArtifact(meta, file, 'create');
1✔
223
  }, [JSON.stringify(creation)]);
224

225
  const addArtifact = (meta, file, type = 'upload') => {
45!
226
    const upload = type === 'create' ? createArtifact(meta, file) : uploadArtifact(meta, file);
2✔
227
    onUploadStarted();
2✔
228
    return upload.then(() => {
2✔
229
      if (!onboardingState.complete && deviceTypes.length && pastCount) {
2!
230
        advanceOnboarding(onboardingSteps.UPLOAD_NEW_ARTIFACT_TIP);
×
231
        if (type === 'create') {
×
232
          advanceOnboarding(onboardingSteps.UPLOAD_NEW_ARTIFACT_DIALOG_CLICK);
×
233
        }
234
      }
235
      // track in GA
236
      Tracking.event({ category: 'artifacts', action: 'create' });
2✔
237
    });
238
  };
239

240
  const onUpdateCreation = update => setCreation(current => ({ ...current, ...update }));
45✔
241

242
  const onNextClick = useCallback(() => {
45✔
243
    if (!onboardingState.complete && creation.destination) {
1!
244
      advanceOnboarding(onboardingSteps.UPLOAD_NEW_ARTIFACT_DIALOG_DEVICE_TYPE);
×
245
    }
246
    onUpdateCreation({ isValid: false });
1✔
247
    setActiveStep(activeStep + 1);
1✔
248
  }, [activeStep, creation.destination, onboardingState.complete]);
249

250
  const onRemove = () => onUpdateCreation({ file: undefined, isValid: false });
45✔
251
  const buttonRef = useRef();
45✔
252

253
  const { file, finalStep, isValid, type } = creation;
45✔
254
  const { component: ComponentToShow } = uploadTypes[type];
45✔
255
  const commonProps = { advanceOnboarding, onboardingState, releases, setSnackbar, updateCreation: onUpdateCreation };
45✔
256

257
  let onboardingComponent = null;
45✔
258
  if (!onboardingState.complete && buttonRef.current && finalStep && isValid) {
45✔
259
    const releaseNameAnchor = {
1✔
260
      left: buttonRef.current.offsetLeft - 15,
261
      top: buttonRef.current.offsetTop + buttonRef.current.clientHeight / 2
262
    };
263
    onboardingComponent = getOnboardingComponentFor(
1✔
264
      onboardingSteps.UPLOAD_NEW_ARTIFACT_DIALOG_CLICK,
265
      onboardingState,
266
      { anchor: releaseNameAnchor, place: 'left' },
267
      onboardingComponent
268
    );
269
  }
270
  return (
45✔
271
    <Dialog open={true} fullWidth={true} maxWidth="sm">
272
      <DialogTitle>Upload an Artifact</DialogTitle>
273
      <DialogContent className="dialog-content margin-top margin-left margin-right margin-bottom">
274
        {!file ? (
45✔
275
          <ArtifactUpload {...commonProps} />
276
        ) : (
277
          <ComponentToShow {...commonProps} activeStep={activeStep} creation={creation} deviceTypes={deviceTypes} onRemove={onRemove} />
278
        )}
279
      </DialogContent>
280
      <DialogActions>
281
        <Button onClick={onCancel}>Cancel</Button>
282
        {!!activeStep && <Button onClick={() => setActiveStep(activeStep - 1)}>Back</Button>}
✔
283
        <div style={{ flexGrow: 1 }} />
284
        {file && (
84✔
285
          <Button variant="contained" color="primary" disabled={!isValid} onClick={() => (finalStep ? onUpload() : onNextClick())} ref={buttonRef}>
3✔
286
            {finalStep ? 'Upload artifact' : 'Next'}
39✔
287
          </Button>
288
        )}
289
        {onboardingComponent}
290
      </DialogActions>
291
    </Dialog>
292
  );
293
};
294

295
export default AddArtifactDialog;
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