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

mendersoftware / gui / 1315496247

03 Jun 2024 07:49AM UTC coverage: 83.437% (-16.5%) from 99.964%
1315496247

Pull #4434

gitlab-ci

mzedel
chore: aligned snapshots with updated mui version

Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #4434: chore: Bump the mui group with 3 updates

4476 of 6391 branches covered (70.04%)

8488 of 10173 relevant lines covered (83.44%)

140.36 hits per line

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

62.86
/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, useState } 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 { removeRelease, selectRelease, setReleasesListState } from '../../actions/releaseActions.js';
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
import AddTagsDialog from './dialogs/addTags.js';
30
import { DeleteReleasesConfirmationDialog, ReleaseQuickActions } from './releasedetails.js';
31

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

65
const useStyles = makeStyles()(() => ({
4✔
66
  empty: { margin: '8vh auto' }
67
}));
68

69
const { page: defaultPage, perPage: defaultPerPage } = DEVICE_LIST_DEFAULTS;
4✔
70

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

91
export const ReleasesList = ({ className = '', onFileUploadClick }) => {
4✔
92
  const repoRef = useRef();
36✔
93
  const dropzoneRef = useRef();
36✔
94
  const uploading = useSelector(state => state.app.uploading);
102✔
95
  const releasesListState = useSelector(getReleaseListState);
36✔
96
  const { selection: selectedRows } = releasesListState;
36✔
97
  const { isLoading, page = defaultPage, perPage = defaultPerPage, searchTerm, sort = {}, searchTotal, selectedTags = [], total, type } = releasesListState;
36!
98
  const hasReleases = useSelector(getHasReleases);
36✔
99
  const features = useSelector(getFeatures);
36✔
100
  const releases = useSelector(getReleasesList);
36✔
101
  const userCapabilities = useSelector(getUserCapabilities);
36✔
102
  const dispatch = useDispatch();
36✔
103
  const { classes } = useStyles();
36✔
104
  const [addTagsDialog, setAddTagsDialog] = useState(false);
36✔
105
  const [deleteDialogConfirmation, setDeleteDialogConfirmation] = useState(false);
36✔
106
  const [selectedReleases, setSelectedReleases] = useState([]);
36✔
107

108
  const { canUploadReleases } = userCapabilities;
36✔
109
  const { key: attribute, direction } = sort;
36✔
110

111
  const onSelect = useCallback(id => dispatch(selectRelease(id)), [dispatch]);
36✔
112

113
  const onChangeSorting = sortKey => {
36✔
114
    let sort = { key: sortKey, direction: direction === SORTING_OPTIONS.asc ? SORTING_OPTIONS.desc : SORTING_OPTIONS.asc };
×
115
    if (sortKey !== attribute) {
×
116
      sort = { ...sort, direction: columns.find(({ key }) => key === sortKey)?.defaultSortDirection ?? SORTING_OPTIONS.desc };
×
117
    }
118
    dispatch(setReleasesListState({ page: 1, sort }));
×
119
  };
120

121
  const onChangePagination = (page, currentPerPage = perPage) => dispatch(setReleasesListState({ page, perPage: currentPerPage }));
36!
122

123
  const onDrop = (acceptedFiles, rejectedFiles) => {
36✔
124
    if (acceptedFiles.length) {
×
125
      onFileUploadClick(acceptedFiles[0]);
×
126
    }
127
    if (rejectedFiles.length) {
×
128
      dispatch(setSnackbar(`File '${rejectedFiles[0].name}' was rejected. File should be of type .mender`, null));
×
129
    }
130
  };
131

132
  const applicableColumns = useMemo(
36✔
133
    () =>
134
      columns.reduce((accu, column) => {
4✔
135
        if (column.canShow({ features })) {
16!
136
          accu.push(column);
16✔
137
        }
138
        return accu;
16✔
139
      }, []),
140
    // eslint-disable-next-line react-hooks/exhaustive-deps
141
    [JSON.stringify(features)]
142
  );
143

144
  const onDeleteRelease = releases => {
36✔
145
    setSelectedReleases(releases);
×
146
    setDeleteDialogConfirmation(true);
×
147
  };
148

149
  const deleteReleases = () => {
36✔
150
    setDeleteDialogConfirmation(false);
×
151
    dispatch(setReleasesListState({ loading: true }))
×
152
      .then(() => {
153
        const deleteRequests = selectedReleases.reduce((accu, release) => {
×
154
          accu.push(dispatch(removeRelease(release.name)));
×
155
          return accu;
×
156
        }, []);
157
        return Promise.all(deleteRequests);
×
158
      })
159
      .then(() => onSelectionChange([]));
×
160
  };
161

162
  const onTagRelease = releases => {
36✔
163
    setSelectedReleases(releases);
×
164
    setAddTagsDialog(true);
×
165
  };
166

167
  const actionCallbacks = {
36✔
168
    onDeleteRelease,
169
    onTagRelease
170
  };
171

172
  const isFiltering = !!(selectedTags.length || type || searchTerm);
36✔
173
  const potentialTotal = isFiltering ? searchTotal : total;
36✔
174
  if (!hasReleases) {
36!
175
    return (
×
176
      <EmptyState
177
        canUpload={canUploadReleases}
178
        className={classes.empty}
179
        dropzoneRef={dropzoneRef}
180
        uploading={uploading}
181
        onDrop={onDrop}
182
        onUpload={onFileUploadClick}
183
      />
184
    );
185
  }
186

187
  const onSelectionChange = (selection = []) => {
36!
188
    dispatch(setReleasesListState({ selection }));
×
189
  };
190
  return (
36✔
191
    <div className={className}>
192
      {isLoading === undefined ? (
36!
193
        <Loader show />
194
      ) : !potentialTotal ? (
36✔
195
        <p className="margin-top muted align-center margin-right">There are no Releases {isFiltering ? 'for the filter selection' : 'yet'}</p>
1!
196
      ) : (
197
        <>
198
          <DetailsTable
199
            columns={applicableColumns}
200
            items={releases}
201
            onItemClick={onSelect}
202
            sort={sort}
203
            onChangeSorting={onChangeSorting}
204
            tableRef={repoRef}
205
            onRowSelected={onSelectionChange}
206
            selectedRows={selectedRows}
207
          />
208
          <div className="flexbox">
209
            <Pagination
210
              className="margin-top-none"
211
              count={potentialTotal}
212
              rowsPerPage={perPage}
213
              onChangePage={onChangePagination}
214
              onChangeRowsPerPage={newPerPage => onChangePagination(1, newPerPage)}
×
215
              page={page}
216
            />
217
            <Loader show={isLoading} small />
218
          </div>
219
          {selectedRows?.length > 0 && <ReleaseQuickActions actionCallbacks={actionCallbacks} userCapabilities={userCapabilities} releases={releases} />}
35!
220
          {addTagsDialog && <AddTagsDialog selectedReleases={selectedReleases} onClose={() => setAddTagsDialog(false)}></AddTagsDialog>}
×
221
          {deleteDialogConfirmation && (
35!
222
            <DeleteReleasesConfirmationDialog onClose={() => setDeleteDialogConfirmation(false)} onSubmit={deleteReleases}></DeleteReleasesConfirmationDialog>
×
223
          )}
224
        </>
225
      )}
226
    </div>
227
  );
228
};
229

230
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