• 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

43.94
/src/js/components/devices/widgets/filters.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, { useEffect, useState } from 'react';
15
import { useDispatch, useSelector } from 'react-redux';
16

17
import { Add as AddIcon } from '@mui/icons-material';
18
// material ui
19
import { Button, Chip, Collapse } from '@mui/material';
20

21
import { getDeviceAttributes, setDeviceFilters, setDeviceListState } from '../../../actions/deviceActions';
22
import { saveGlobalSettings } from '../../../actions/userActions';
23
import { DEVICE_FILTERING_OPTIONS, emptyFilter } from '../../../constants/deviceConstants';
24
import { deepCompare } from '../../../helpers';
25
import { getDeviceFilters, getFilterAttributes, getIsEnterprise, getOrganization, getSelectedGroupInfo, getTenantCapabilities } from '../../../selectors';
26
import EnterpriseNotification from '../../common/enterpriseNotification';
27
import MenderTooltip from '../../common/mendertooltip';
28
import FilterItem from './filteritem';
29

30
export const getFilterLabelByKey = (key, attributes) => {
13✔
31
  const attr = attributes.find(attr => attr.key === key);
144✔
32
  return attr?.value ?? key ?? '';
14✔
33
};
34

35
const MAX_PREVIOUS_FILTERS_COUNT = 3;
13✔
36

37
export const Filters = ({ className = '', filters: propsFilters, isModification = true, onFilterChange, onGroupClick, open }) => {
13✔
38
  const [adding, setAdding] = useState(isModification);
25✔
39
  const [newFilter, setNewFilter] = useState(emptyFilter);
25✔
40
  const [currentFilters, setCurrentFilters] = useState([]);
25✔
41
  const [editedIndex, setEditedIndex] = useState(0);
25✔
42
  const dispatch = useDispatch();
25✔
43
  const { plan = 'os' } = useSelector(getOrganization);
25!
44
  const { groupFilters, selectedGroup } = useSelector(getSelectedGroupInfo);
25✔
45
  const attributes = useSelector(getFilterAttributes);
25✔
46
  const { hasFullFiltering: canFilterMultiple } = useSelector(getTenantCapabilities);
25✔
47
  const filters = propsFilters || useSelector(getDeviceFilters);
25✔
48
  const isEnterprise = useSelector(getIsEnterprise);
25✔
49
  const previousFilters = useSelector(state => state.users.globalSettings.previousFilters);
54✔
50

51
  useEffect(() => {
25✔
52
    setCurrentFilters(filters);
4✔
53
    setEditedIndex(filters.length);
4✔
54
    dispatch(getDeviceAttributes());
4✔
55
  }, [open]);
56

57
  useEffect(() => {
25✔
58
    setAdding(adding && groupFilters.length ? isModification : true);
4!
59
    setNewFilter(emptyFilter);
4✔
60
  }, [isModification, groupFilters.length]);
61

62
  const updateFilter = newFilter => {
25✔
63
    setNewFilter(newFilter);
×
64
    saveUpdatedFilter(newFilter);
×
65
    let changedFilters = [...currentFilters];
×
66
    if (editedIndex == currentFilters.length) {
×
67
      changedFilters.push(newFilter);
×
68
      return handleFilterChange(changedFilters);
×
69
    }
70
    changedFilters[editedIndex] = newFilter;
×
71
    handleFilterChange(changedFilters);
×
72
  };
73

74
  const saveUpdatedFilter = newFilter => {
25✔
75
    let changedPreviousFilters = [...previousFilters];
×
76
    if (!changedPreviousFilters.find(filter => deepCompare(filter, newFilter))) {
×
77
      changedPreviousFilters.push(newFilter);
×
78
      dispatch(saveGlobalSettings({ previousFilters: changedPreviousFilters.slice(-1 * MAX_PREVIOUS_FILTERS_COUNT) }));
×
79
    }
80
  };
81

82
  const resetIdFilter = () => dispatch(setDeviceListState({ selectedId: undefined, setOnly: true }));
25✔
83

84
  const removeFilter = removedFilter => {
25✔
85
    if (removedFilter.key === 'id') {
×
86
      resetIdFilter();
×
87
    }
88
    let changedFilters = filters.filter(filter => !deepCompare(filter, removedFilter));
×
89
    handleFilterChange(changedFilters);
×
90
    if (deepCompare(newFilter, removedFilter)) {
×
91
      setNewFilter(emptyFilter);
×
92
    }
93
    const currentFilters = changedFilters.filter(filter => !deepCompare(filter, newFilter));
×
94
    setCurrentFilters(currentFilters);
×
95
    setEditedIndex(currentFilters.length);
×
96
    if (!currentFilters.length) {
×
97
      setAdding(true);
×
98
    }
99
  };
100

101
  const clearFilters = () => {
25✔
102
    handleFilterChange([]);
×
103
    resetIdFilter();
×
104
    setCurrentFilters([]);
×
105
    setEditedIndex(0);
×
106
    setNewFilter(emptyFilter);
×
107
  };
108

109
  const onAddClick = () => {
25✔
110
    setAdding(true);
×
111
    setEditedIndex(filters.length);
×
112
    if (Object.values(newFilter).every(thing => !!thing)) {
×
113
      setCurrentFilters([...currentFilters, newFilter]);
×
114
    }
115
    setNewFilter(emptyFilter);
×
116
  };
117

118
  const handleFilterChange = filters => {
25✔
119
    const activeFilters = filters.filter(item => item.value !== '');
×
120
    dispatch(setDeviceFilters(activeFilters));
×
121
    onFilterChange();
×
122
    if (activeFilters.length === 0) {
×
123
      setAdding(true);
×
124
    }
125
  };
126

127
  const filter = filters.find(filter => deepCompare(filter, newFilter)) || newFilter;
25✔
128
  const isFilterDefined = filter && Object.values(filter).every(thing => !!thing);
25✔
129
  const addButton = <Chip icon={<AddIcon />} label="Add a rule" color="primary" onClick={onAddClick} />;
25✔
130
  return (
25✔
131
    <Collapse in={open} timeout="auto" className={`${className} filter-wrapper`} unmountOnExit>
132
      <>
133
        <div className="flexbox">
134
          <div className="margin-right" style={{ marginTop: currentFilters.length ? 8 : 25 }}>
25!
135
            Devices matching:
136
          </div>
137
          <div>
138
            {currentFilters.length ? (
25!
139
              <div className="filter-list">
140
                {currentFilters.map(item => (
141
                  <Chip
×
142
                    className="margin-right-small"
143
                    key={`filter-${item.key}-${item.operator}-${item.value}`}
144
                    label={`${getFilterLabelByKey(item.key, attributes)} ${DEVICE_FILTERING_OPTIONS[item.operator].shortform} ${
145
                      item.operator !== '$exists' && item.operator !== '$nexists' ? (item.operator === '$regex' ? `${item.value}.*` : item.value) : ''
×
146
                    }`}
147
                    onDelete={() => removeFilter(item)}
×
148
                  />
149
                ))}
150
                {!adding && addButton}
×
151
              </div>
152
            ) : null}
153
            {adding && <FilterItem attributes={attributes} filter={filter} onRemove={removeFilter} onSelect={updateFilter} plan={plan} />}
47✔
154
            {isFilterDefined && addButton}
25!
155
          </div>
156
        </div>
157
        <div className="flexbox column margin-top-small margin-bottom-small" style={{ alignItems: 'flex-end' }}>
158
          {!!filters.length && !groupFilters.length && (
25!
159
            <span className="link margin-small margin-top-none" onClick={clearFilters}>
160
              Clear filter
161
            </span>
162
          )}
163
          <EnterpriseNotification
164
            isEnterprise={isEnterprise}
165
            benefit="filtering by multiple attributes to improve the device overview and the creation of dynamic groups to ease device management"
166
          />
167
          {canFilterMultiple && isEnterprise && filters.length >= 1 && (
61!
168
            <>
169
              {selectedGroup ? (
×
170
                !!groupFilters.length && (
×
171
                  <MenderTooltip
172
                    title="Saved changes will not change the target devices of any ongoing deployments to this group, but will take effect for new deployments"
173
                    arrow
174
                  >
175
                    <Button variant="contained" color="secondary" onClick={onGroupClick}>
176
                      Save group
177
                    </Button>
178
                  </MenderTooltip>
179
                )
180
              ) : (
181
                <Button variant="contained" color="secondary" onClick={onGroupClick}>
182
                  Create group with this filter
183
                </Button>
184
              )}
185
            </>
186
          )}
187
        </div>
188
      </>
189
    </Collapse>
190
  );
191
};
192

193
export default Filters;
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