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

mendersoftware / gui / 988636826

01 Sep 2023 04:04AM UTC coverage: 82.384% (-17.6%) from 99.964%
988636826

Pull #3969

gitlab-ci

web-flow
chore: Bump autoprefixer from 10.4.14 to 10.4.15

Bumps [autoprefixer](https://github.com/postcss/autoprefixer) from 10.4.14 to 10.4.15.
- [Release notes](https://github.com/postcss/autoprefixer/releases)
- [Changelog](https://github.com/postcss/autoprefixer/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/autoprefixer/compare/10.4.14...10.4.15)

---
updated-dependencies:
- dependency-name: autoprefixer
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #3969: chore: Bump autoprefixer from 10.4.14 to 10.4.15

4346 of 6321 branches covered (0.0%)

8259 of 10025 relevant lines covered (82.38%)

192.73 hits per line

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

67.07
/src/js/components/releases/releasedetails.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, { useMemo, useRef, useState } from 'react';
15
import { useDispatch, useSelector } from 'react-redux';
16
import { useNavigate } from 'react-router-dom';
17

18
// material ui
19
import {
20
  Close as CloseIcon,
21
  Edit as EditIcon,
22
  HighlightOffOutlined as HighlightOffOutlinedIcon,
23
  Link as LinkIcon,
24
  Replay as ReplayIcon,
25
  Sort as SortIcon
26
} from '@mui/icons-material';
27
import { Button, Collapse, Divider, Drawer, IconButton, SpeedDial, SpeedDialAction, SpeedDialIcon, Tooltip } from '@mui/material';
28
import { speedDialActionClasses } from '@mui/material/SpeedDialAction';
29
import { makeStyles } from 'tss-react/mui';
30

31
import copy from 'copy-to-clipboard';
32

33
import { setSnackbar } from '../../actions/appActions';
34
import { removeArtifact, removeRelease, selectArtifact, selectRelease } from '../../actions/releaseActions';
35
import { DEPLOYMENT_ROUTES } from '../../constants/deploymentConstants';
36
import { FileSize, customSort, formatTime, toggle } from '../../helpers';
37
import { getFeatures, getUserCapabilities } from '../../selectors';
38
import useWindowSize from '../../utils/resizehook';
39
import ChipSelect from '../common/chipselect';
40
import { RelativeTime } from '../common/time';
41
import { HELPTOOLTIPS, MenderHelpTooltip } from '../helptips/helptooltips';
42
import Artifact from './artifact';
43
import RemoveArtifactDialog from './dialogs/removeartifact';
44

45
const DeviceTypeCompatibility = ({ artifact }) => {
6✔
46
  const compatible = artifact.artifact_depends ? artifact.artifact_depends.device_type.join(', ') : artifact.device_types_compatible.join(', ');
18✔
47
  return (
18✔
48
    <Tooltip title={compatible} placement="top-start">
49
      <div className="text-overflow">{compatible}</div>
50
    </Tooltip>
51
  );
52
};
53

54
export const columns = [
6✔
55
  {
56
    title: 'Device type compatibility',
57
    name: 'device_types',
58
    sortable: false,
59
    render: DeviceTypeCompatibility,
60
    tooltip: <MenderHelpTooltip id={HELPTOOLTIPS.expandArtifact.id} className="margin-left-small" />
61
  },
62
  {
63
    title: 'Type',
64
    name: 'type',
65
    sortable: false,
66
    render: ({ artifact }) => <div style={{ maxWidth: '100vw' }}>{artifact.updates.reduce((accu, item) => (accu ? accu : item.type_info.type), '')}</div>
18!
67
  },
68
  { title: 'Size', name: 'size', sortable: true, render: ({ artifact }) => <FileSize fileSize={artifact.size} /> },
18✔
69
  { title: 'Last modified', name: 'modified', sortable: true, render: ({ artifact }) => <RelativeTime updateTime={formatTime(artifact.modified)} /> }
18✔
70
];
71

72
const defaultActions = [
6✔
73
  {
74
    action: ({ onCreateDeployment, selection }) => onCreateDeployment(selection),
×
75
    icon: <ReplayIcon />,
76
    isApplicable: ({ userCapabilities: { canDeploy } }) => canDeploy,
2✔
77
    key: 'deploy',
78
    title: 'Create a deployment for this release'
79
  },
80
  {
81
    action: ({ onDeleteRelease, selection }) => onDeleteRelease(selection),
×
82
    icon: <HighlightOffOutlinedIcon className="red" />,
83
    isApplicable: ({ userCapabilities: { canManageReleases } }) => canManageReleases,
2✔
84
    key: 'delete',
85
    title: 'Delete release'
86
  }
87
];
88

89
const useStyles = makeStyles()(theme => ({
6✔
90
  container: {
91
    display: 'flex',
92
    position: 'fixed',
93
    bottom: theme.spacing(6.5),
94
    right: theme.spacing(6.5),
95
    zIndex: 10,
96
    minWidth: 'max-content',
97
    alignItems: 'flex-end',
98
    justifyContent: 'flex-end',
99
    pointerEvents: 'none',
100
    [`.${speedDialActionClasses.staticTooltipLabel}`]: {
101
      minWidth: 'max-content'
102
    }
103
  },
104
  fab: { margin: theme.spacing(2) },
105
  tagSelect: { maxWidth: 350 },
106
  label: {
107
    marginRight: theme.spacing(2),
108
    marginBottom: theme.spacing(4)
109
  }
110
}));
111

112
export const ReleaseQuickActions = ({ actionCallbacks, innerRef, selectedRelease, userCapabilities }) => {
6✔
113
  const [showActions, setShowActions] = useState(false);
20✔
114
  const { classes } = useStyles();
20✔
115

116
  const actions = useMemo(() => {
20✔
117
    return Object.values(defaultActions).reduce((accu, action) => {
2✔
118
      if (action.isApplicable({ userCapabilities })) {
4!
119
        accu.push(action);
4✔
120
      }
121
      return accu;
4✔
122
    }, []);
123
    // eslint-disable-next-line react-hooks/exhaustive-deps
124
  }, [JSON.stringify(userCapabilities)]);
125

126
  return (
20✔
127
    <div className={classes.container} ref={innerRef}>
128
      <div className={classes.label}>Release actions</div>
129
      <SpeedDial
130
        className={classes.fab}
131
        ariaLabel="device-actions"
132
        icon={<SpeedDialIcon />}
133
        onClose={() => setShowActions(false)}
×
134
        onOpen={setShowActions}
135
        open={Boolean(showActions)}
136
      >
137
        {actions.map(action => (
138
          <SpeedDialAction
40✔
139
            key={action.key}
140
            aria-label={action.key}
141
            icon={action.icon}
142
            tooltipTitle={action.title}
143
            tooltipOpen
144
            onClick={() => action.action({ ...actionCallbacks, selection: selectedRelease })}
×
145
          />
146
        ))}
147
      </SpeedDial>
148
    </div>
149
  );
150
};
151

152
const ReleaseTags = ({ existingTags = [] }) => {
6!
153
  const [selectedTags, setSelectedTags] = useState(existingTags);
×
154
  const [isEditing, setIsEditing] = useState(false);
×
155

156
  const onToggleEdit = () => {
×
157
    setSelectedTags(existingTags);
×
158
    setIsEditing(toggle);
×
159
  };
160

161
  const onTagSelectionChanged = ({ selection }) => setSelectedTags(selection);
×
162

163
  const onSave = () => {
×
164
    console.log('saving tags', selectedTags);
×
165
  };
166

167
  const { classes } = useStyles();
×
168

169
  return (
×
170
    <div className="margin-bottom" style={{ maxWidth: 500 }}>
171
      <div className="flexbox center-aligned">
172
        <h4 className="margin-right">Tags</h4>
173
        {!isEditing && (
×
174
          <Button onClick={onToggleEdit} size="small" startIcon={<EditIcon />}>
175
            Edit
176
          </Button>
177
        )}
178
      </div>
179
      <ChipSelect
180
        className={classes.tagSelect}
181
        id="release-tags"
182
        label=""
183
        onChange={onTagSelectionChanged}
184
        disabled={!isEditing}
185
        key={`${isEditing}`}
186
        placeholder={isEditing ? 'Enter release tags' : 'Click edit to add release tags'}
×
187
        selection={selectedTags}
188
        options={existingTags}
189
      />
190
      <Collapse in={isEditing}>
191
        <div className="flexbox center-aligned margin-top-small" style={{ justifyContent: 'end' }}>
192
          <Button variant="contained" onClick={onSave} color="secondary" style={{ marginRight: 10 }}>
193
            Save
194
          </Button>
195
          <Button onClick={onToggleEdit}>Cancel</Button>
196
        </div>
197
      </Collapse>
198
    </div>
199
  );
200
};
201

202
const ArtifactsList = ({ artifacts, selectArtifact, selectedArtifact, setShowRemoveArtifactDialog }) => {
6✔
203
  const [sortCol, setSortCol] = useState('modified');
20✔
204
  const [sortDown, setSortDown] = useState(true);
20✔
205

206
  const onRowSelection = artifact => {
20✔
207
    if (!artifact || !selectedArtifact || selectedArtifact.id !== artifact.id) {
×
208
      return selectArtifact(artifact);
×
209
    }
210
    selectArtifact();
×
211
  };
212

213
  const sortColumn = col => {
20✔
214
    if (!col.sortable) {
×
215
      return;
×
216
    }
217
    // sort table
218
    setSortDown(toggle);
×
219
    setSortCol(col);
×
220
  };
221

222
  if (!artifacts.length) {
20✔
223
    return null;
3✔
224
  }
225

226
  const items = artifacts.sort(customSort(sortDown, sortCol));
17✔
227

228
  return (
17✔
229
    <>
230
      <h4>Artifacts in this Release:</h4>
231
      <div>
232
        <div className="release-repo-item repo-item repo-header">
233
          {columns.map(item => (
234
            <div className="columnHeader" key={item.name} onClick={() => sortColumn(item)}>
68✔
235
              <Tooltip title={item.title} placement="top-start">
236
                {item.title}
237
              </Tooltip>
238
              {item.sortable ? <SortIcon className={`sortIcon ${sortCol === item.name ? 'selected' : ''} ${sortDown.toString()}`} /> : null}
102✔
239
              {item.tooltip}
240
            </div>
241
          ))}
242
          <div style={{ width: 48 }} />
243
        </div>
244
        {items.map((pkg, index) => {
245
          const expanded = !!(selectedArtifact && selectedArtifact.id === pkg.id);
17✔
246
          return (
17✔
247
            <Artifact
248
              key={`repository-item-${index}`}
249
              artifact={pkg}
250
              columns={columns}
251
              expanded={expanded}
252
              index={index}
253
              onRowSelection={() => onRowSelection(pkg)}
×
254
              // this will be run after expansion + collapse and both need some time to fully settle
255
              // otherwise the measurements are off
256
              showRemoveArtifactDialog={setShowRemoveArtifactDialog}
257
            />
258
          );
259
        })}
260
      </div>
261
    </>
262
  );
263
};
264

265
export const ReleaseDetails = () => {
6✔
266
  const [showRemoveDialog, setShowRemoveArtifactDialog] = useState(false);
36✔
267
  const [confirmReleaseDeletion, setConfirmReleaseDeletion] = useState(false);
35✔
268
  // eslint-disable-next-line no-unused-vars
269
  const windowSize = useWindowSize();
35✔
270
  const creationRef = useRef();
35✔
271
  const drawerRef = useRef();
35✔
272
  const navigate = useNavigate();
35✔
273
  const dispatch = useDispatch();
35✔
274
  const { hasReleaseTags } = useSelector(getFeatures);
35✔
275
  const release = useSelector(state => state.releases.byId[state.releases.selectedRelease]) ?? {};
60✔
276
  const selectedArtifact = useSelector(state => state.releases.selectedArtifact);
60✔
277
  const userCapabilities = useSelector(getUserCapabilities);
35✔
278

279
  const onRemoveArtifact = artifact => dispatch(removeArtifact(artifact.id)).finally(() => setShowRemoveArtifactDialog(false));
35✔
280

281
  const copyLinkToClipboard = () => {
35✔
282
    const location = window.location.href.substring(0, window.location.href.indexOf('/releases') + '/releases'.length);
×
283
    copy(`${location}/${release.Name}`);
×
284
    dispatch(setSnackbar('Link copied to clipboard'));
×
285
  };
286

287
  const onCloseClick = () => dispatch(selectRelease());
35✔
288

289
  const onCreateDeployment = () => navigate(`${DEPLOYMENT_ROUTES.active.route}?open=true&release=${encodeURIComponent(release.Name)}`);
35✔
290

291
  const onToggleReleaseDeletion = () => setConfirmReleaseDeletion(toggle);
35✔
292

293
  const onDeleteRelease = () => dispatch(removeRelease(release.Name)).then(() => setConfirmReleaseDeletion(false));
35✔
294

295
  const artifacts = release.Artifacts ?? [];
35✔
296
  return (
35✔
297
    <Drawer anchor="right" open={!!release.Name} onClose={onCloseClick} PaperProps={{ style: { minWidth: '60vw' }, ref: drawerRef }}>
298
      <div className="flexbox center-aligned space-between">
299
        <div className="flexbox center-aligned">
300
          <b>
301
            Release information for <i>{release.Name}</i>
302
          </b>
303
          <IconButton onClick={copyLinkToClipboard} size="large">
304
            <LinkIcon />
305
          </IconButton>
306
        </div>
307
        <div className="flexbox center-aligned">
308
          <div className="muted margin-right flexbox">
309
            <div className="margin-right-small">Last modified:</div>
310
            <RelativeTime updateTime={release.modified} />
311
          </div>
312
          <IconButton onClick={onCloseClick} aria-label="close" size="large">
313
            <CloseIcon />
314
          </IconButton>
315
        </div>
316
      </div>
317
      <Divider className="margin-bottom" />
318
      {hasReleaseTags && <ReleaseTags />}
35!
319
      <ArtifactsList
320
        artifacts={artifacts}
321
        selectArtifact={artifact => dispatch(selectArtifact(artifact))}
×
322
        selectedArtifact={selectedArtifact}
323
        setShowRemoveArtifactDialog={setShowRemoveArtifactDialog}
324
      />
325
      <RemoveArtifactDialog
326
        artifact={selectedArtifact}
327
        open={!!showRemoveDialog}
328
        onCancel={() => setShowRemoveArtifactDialog(false)}
2✔
329
        onRemove={() => onRemoveArtifact(selectedArtifact)}
×
330
      />
331
      <RemoveArtifactDialog open={!!confirmReleaseDeletion} onRemove={onDeleteRelease} onCancel={onToggleReleaseDeletion} release={release} />
332
      <ReleaseQuickActions
333
        actionCallbacks={{ onCreateDeployment, onDeleteRelease: onToggleReleaseDeletion }}
334
        innerRef={creationRef}
335
        selectedRelease={release}
336
        userCapabilities={userCapabilities}
337
      />
338
    </Drawer>
339
  );
340
};
341

342
export default ReleaseDetails;
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