• 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

76.39
/src/js/components/releases/artifactdetails.js
1
// Copyright 2015 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, useMemo, useState } from 'react';
15
import { useDispatch, useSelector } from 'react-redux';
16

17
// material ui
18
import {
19
  Add as AddIcon,
20
  Cancel as CancelIcon,
21
  CancelOutlined as CancelOutlinedIcon,
22
  CheckCircleOutline as CheckCircleOutlineIcon,
23
  Check as CheckIcon,
24
  Edit as EditIcon,
25
  ExitToApp as ExitToAppIcon,
26
  Launch as LaunchIcon,
27
  Remove as RemoveIcon
28
} from '@mui/icons-material';
29
import { Accordion, AccordionDetails, AccordionSummary, Button, IconButton, Input, InputAdornment, List, ListItem, ListItemText } from '@mui/material';
30
import { makeStyles } from 'tss-react/mui';
31

32
import pluralize from 'pluralize';
33

34
import { editArtifact, getArtifactInstallCount, getArtifactUrl } from '../../actions/releaseActions';
35
import { extractSoftware, extractSoftwareItem, toggle } from '../../helpers';
36
import { getUserCapabilities } from '../../selectors';
37
import ExpandableAttribute from '../common/expandable-attribute';
38
import ArtifactPayload from './artifactPayload';
39
import ArtifactMetadataList from './artifactmetadatalist';
40

41
const useStyles = makeStyles()(theme => ({
7✔
42
  editButton: {
43
    color: 'rgba(0, 0, 0, 0.54)',
44
    marginBottom: 10
45
  },
46
  link: { marginTop: theme.spacing() },
47
  listItemStyle: {
48
    bordered: {
49
      borderBottom: '1px solid',
50
      borderBottomColor: theme.palette.grey[500]
51
    },
52
    color: theme.palette.text.primary,
53
    fontSize: 13,
54
    marginRight: '2vw',
55
    minWidth: 200,
56
    padding: 0,
57
    width: 'initial'
58
  },
59
  paddingOverride: { paddingBottom: 4, paddingTop: 0 },
60
  accordPanel1: {
61
    background: theme.palette.grey[500],
62
    borderTop: 'none',
63
    padding: '0 15px',
64
    marginBottom: 30,
65
    [`&.Mui-expanded`]: {
66
      background: theme.palette.grey[500],
67
      marginBottom: 30
68
    }
69
  },
70
  accordSummary: {
71
    background: theme.palette.grey[500],
72
    padding: 0
73
  }
74
}));
75

76
export const transformArtifactCapabilities = (capabilities = {}) => {
7✔
77
  return Object.entries(capabilities).reduce((accu, [key, value]) => {
60✔
78
    if (!Array.isArray(value)) {
73✔
79
      accu.push({ key, primary: key, secondary: value });
37✔
80
    } else if (!key.startsWith('device_type')) {
36✔
81
      // we can expect this to be an array of artifacts or artifact groups this artifact depends on
82
      const dependencies = value.reduce((dependencies, dependency, index) => {
18✔
83
        const dependencyKey = value.length > 1 ? `${key}-${index + 1}` : key;
36!
84
        dependencies.push({ key: dependencyKey, primary: dependencyKey, secondary: dependency });
36✔
85
        return dependencies;
36✔
86
      }, []);
87
      accu.push(...dependencies);
18✔
88
    }
89
    return accu;
73✔
90
  }, []);
91
};
92

93
export const transformArtifactMetadata = (metadata = {}) => {
7✔
94
  return Object.entries(metadata).reduce((accu, [key, value]) => {
20✔
95
    const commonProps = { key, primary: key, secondaryTypographyProps: { component: 'div' } };
4✔
96
    if (Array.isArray(value)) {
4✔
97
      accu.push({ ...commonProps, secondary: value.length ? value.join(',') : '-' });
1!
98
    } else if (value instanceof Object) {
3✔
99
      accu.push({ ...commonProps, secondary: JSON.stringify(value) || '-' });
1!
100
    } else {
101
      accu.push({ ...commonProps, secondary: value || '-' });
2✔
102
    }
103
    return accu;
4✔
104
  }, []);
105
};
106

107
const DevicesLink = ({ artifact: { installCount }, softwareItem: { key, name, version } }) => {
7✔
108
  const { classes } = useStyles();
×
109
  const text = `${installCount} ${pluralize('device', installCount)}`;
×
110
  if (!installCount) {
×
111
    return <div className={classes.link}>{text}</div>;
×
112
  }
113
  const attribute = `${key}${name ? `.${name}` : ''}.version`;
×
114
  return (
×
115
    <a
116
      className={`flexbox center-aligned ${classes.link}`}
117
      href={`${window.location.origin}/ui/devices/accepted?inventory=${attribute}:eq:${version}`}
118
      target="_blank"
119
      rel="noreferrer"
120
    >
121
      {text}
122
      <LaunchIcon className="margin-left-small" fontSize="small" />
123
    </a>
124
  );
125
};
126

127
export const ArtifactDetails = ({ artifact, open, showRemoveArtifactDialog }) => {
7✔
128
  const { classes } = useStyles();
19✔
129
  const [descEdit, setDescEdit] = useState(false);
19✔
130
  const [description, setDescription] = useState(artifact.description);
19✔
131
  const [showPayloads, setShowPayloads] = useState(false);
19✔
132
  const [showProvidesDepends, setShowProvidesDepends] = useState(false);
19✔
133

134
  const dispatch = useDispatch();
19✔
135

136
  const { canManageReleases } = useSelector(getUserCapabilities);
19✔
137

138
  const softwareVersions = useMemo(() => {
19✔
139
    const { software } = extractSoftware(artifact.artifact_provides);
4✔
140
    return software.reduce((accu, item) => {
4✔
141
      const infoItems = item[0].split('.');
2✔
142
      if (infoItems[infoItems.length - 1] !== 'version') {
2!
143
        return accu;
×
144
      }
145
      accu.push({ key: infoItems[0], name: infoItems.slice(1, infoItems.length - 1).join('.'), version: item[1], nestingLevel: infoItems.length });
2✔
146
      return accu;
2✔
147
    }, []);
148
    // eslint-disable-next-line react-hooks/exhaustive-deps
149
  }, [JSON.stringify(artifact.artifact_provides)]);
150

151
  useEffect(() => {
19✔
152
    if (artifact.url || !open) {
4✔
153
      return;
2✔
154
    }
155
    dispatch(getArtifactUrl(artifact.id));
2✔
156
  }, [artifact.id, artifact.url, dispatch, open]);
157

158
  useEffect(() => {
19✔
159
    if (artifact.installCount || !open || softwareVersions.length > 1) {
4✔
160
      return;
2✔
161
    }
162
    const { version } = softwareVersions.sort((a, b) => a.nestingLevel - b.nestingLevel).reduce((accu, item) => accu ?? item, undefined) ?? {};
2!
163
    if (version) {
2!
164
      dispatch(getArtifactInstallCount(artifact.id));
2✔
165
    }
166
    // eslint-disable-next-line react-hooks/exhaustive-deps
167
  }, [artifact.id, artifact.installCount, dispatch, open, softwareVersions.length]);
168

169
  const onToggleEditing = useCallback(
19✔
170
    event => {
171
      event.stopPropagation();
×
172
      if (event.keyCode === 13 || !event.keyCode) {
×
173
        if (descEdit) {
×
174
          // save change
175
          dispatch(editArtifact(artifact.id, { description }));
×
176
        }
177
        setDescEdit(!descEdit);
×
178
      }
179
    },
180
    [artifact.id, descEdit, description, dispatch]
181
  );
182

183
  const softwareItem = extractSoftwareItem(artifact.artifact_provides);
19✔
184
  const softwareInformation = softwareItem
19✔
185
    ? {
186
        title: 'Software versioning information',
187
        content: [
188
          { primary: 'Software filesystem', secondary: softwareItem.key },
189
          { primary: 'Software name', secondary: softwareItem.name },
190
          { primary: 'Software version', secondary: softwareItem.version }
191
        ]
192
      }
193
    : { content: [] };
194

195
  const artifactMetaInfo = [
19✔
196
    { title: 'Depends', content: transformArtifactCapabilities(artifact.artifact_depends) },
197
    { title: 'Clears', content: transformArtifactCapabilities(artifact.artifact_clears) },
198
    { title: 'Provides', content: transformArtifactCapabilities(artifact.artifact_provides) },
199
    { title: 'Artifact metadata', content: transformArtifactMetadata(artifact.metaData) }
200
  ];
201
  const hasMetaInfo = artifactMetaInfo.some(item => !!item.content.length);
59✔
202
  const { installCount } = artifact;
19✔
203
  const itemProps = { classes: { root: 'attributes', disabled: 'opaque' }, className: classes.listItemStyle };
19✔
204
  return (
19✔
205
    <div className={artifact.name == null ? 'muted' : null}>
19✔
206
      <List className="list-horizontal-flex">
207
        <ListItem {...itemProps}>
208
          <ListItemText
209
            primary="Description"
210
            style={{ marginBottom: -3, minWidth: 600 }}
211
            primaryTypographyProps={{ style: { marginBottom: 3 } }}
212
            secondary={
213
              <Input
214
                id="artifact-description"
215
                type="text"
216
                disabled={!descEdit}
217
                value={description}
218
                placeholder="-"
219
                onKeyDown={onToggleEditing}
220
                style={{ width: '100%' }}
221
                onChange={e => setDescription(e.target.value)}
×
222
                endAdornment={
223
                  <InputAdornment position="end">
224
                    <IconButton className={classes.editButton} onClick={onToggleEditing} size="large">
225
                      {descEdit ? <CheckIcon /> : <EditIcon />}
19!
226
                    </IconButton>
227
                  </InputAdornment>
228
                }
229
              />
230
            }
231
            secondaryTypographyProps={{ component: 'div' }}
232
          />
233
        </ListItem>
234
        <ListItem {...itemProps} className={`${classes.listItemStyle} ${classes.listItemStyle.bordered}`}>
235
          <ListItemText primary="Signed" secondary={artifact.signed ? <CheckCircleOutlineIcon className="green" /> : <CancelOutlinedIcon className="red" />} />
19!
236
        </ListItem>
237
        {installCount !== undefined && softwareVersions.length === 1 && (
19!
238
          <ExpandableAttribute
239
            classes={{ root: classes.paddingOverride }}
240
            disableGutters
241
            primary="Installed on"
242
            secondary={<DevicesLink artifact={artifact} softwareItem={softwareItem} />}
243
            secondaryTypographyProps={{ title: `installed on ${installCount} ${pluralize('device', installCount)}` }}
244
            style={{ padding: 0 }}
245
          />
246
        )}
247
      </List>
248
      <ArtifactMetadataList metaInfo={softwareInformation} />
249
      <Accordion square expanded={showPayloads} onChange={() => setShowPayloads(toggle)} className={classes.accordPanel1}>
×
250
        <AccordionSummary className={classes.accordSummary}>
251
          <p>Artifact contents</p>
252
          <div style={{ marginLeft: 'auto' }}>{showPayloads ? <RemoveIcon /> : <AddIcon />}</div>
19!
253
        </AccordionSummary>
254
        <AccordionDetails className={classes.accordSummary}>
255
          {showPayloads &&
19!
256
            !!artifact.updates.length &&
257
            artifact.updates.map((update, index) => <ArtifactPayload index={index} payload={update} key={`artifact-update-${index}`} />)}
×
258
        </AccordionDetails>
259
      </Accordion>
260
      {hasMetaInfo && (
36✔
261
        <Accordion square expanded={showProvidesDepends} onChange={() => setShowProvidesDepends(!showProvidesDepends)} className={classes.accordPanel1}>
×
262
          <AccordionSummary className={classes.accordSummary}>
263
            <p>Provides and Depends</p>
264
            <div style={{ marginLeft: 'auto' }}>{showProvidesDepends ? <RemoveIcon /> : <AddIcon />}</div>
17!
265
          </AccordionSummary>
266
          <AccordionDetails className={classes.accordSummary}>
267
            {showProvidesDepends && artifactMetaInfo.map((info, index) => <ArtifactMetadataList metaInfo={info} key={`artifact-info-${index}`} />)}
×
268
          </AccordionDetails>
269
        </Accordion>
270
      )}
271
      <div className="two-columns margin-top-small" style={{ maxWidth: 'fit-content' }}>
272
        {canManageReleases && (
38✔
273
          <>
274
            <Button
275
              href={artifact.url}
276
              target="_blank"
277
              disabled={!artifact.url}
278
              download={artifact.name ? `${artifact.name}.mender` : true}
19✔
279
              startIcon={<ExitToAppIcon style={{ transform: 'rotateZ(90deg)' }} />}
280
            >
281
              Download Artifact
282
            </Button>
283
            <Button onClick={showRemoveArtifactDialog} startIcon={<CancelIcon className="red auth" />}>
284
              Remove this Artifact?
285
            </Button>
286
          </>
287
        )}
288
      </div>
289
    </div>
290
  );
291
};
292

293
export default ArtifactDetails;
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