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

mendersoftware / gui / 1057188406

01 Nov 2023 04:24AM UTC coverage: 82.824% (-17.1%) from 99.964%
1057188406

Pull #4134

gitlab-ci

web-flow
chore: Bump uuid from 9.0.0 to 9.0.1

Bumps [uuid](https://github.com/uuidjs/uuid) from 9.0.0 to 9.0.1.
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v9.0.0...v9.0.1)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #4134: chore: Bump uuid from 9.0.0 to 9.0.1

4349 of 6284 branches covered (0.0%)

8313 of 10037 relevant lines covered (82.82%)

200.97 hits per line

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

60.78
/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, { useCallback, 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 { BENEFITS } from '../../../constants/appConstants';
24
import { DEVICE_FILTERING_OPTIONS, emptyFilter } from '../../../constants/deviceConstants';
25
import { deepCompare, toggle } from '../../../helpers';
26
import { getDeviceFilters, getFilterAttributes, getGlobalSettings, getIsEnterprise, getSelectedGroupInfo, getTenantCapabilities } from '../../../selectors';
27
import EnterpriseNotification from '../../common/enterpriseNotification';
28
import { InfoHintContainer } from '../../common/info-hint';
29
import MenderTooltip from '../../common/mendertooltip';
30
import FilterItem from './filteritem';
31

32
export const getFilterLabelByKey = (key, attributes) => {
184✔
33
  const attr = attributes.find(attr => attr.key === key);
52✔
34
  return attr?.value ?? key ?? '';
14!
35
};
36

37
const MAX_PREVIOUS_FILTERS_COUNT = 3;
184✔
38

39
const filterCompare = (filter, item) => Object.keys(emptyFilter).every(key => item[key].toString() === filter[key].toString());
184✔
40

41
export const filtersFilter = (item, index, array) => {
184✔
42
  const firstIndex = array.findIndex(filter => filterCompare(filter, item));
6✔
43
  return firstIndex === index;
5✔
44
};
45

46
export const Filters = ({ className = '', onGroupClick, open }) => {
184✔
47
  const [reset, setReset] = useState(false);
25✔
48
  const [newFilter, setNewFilter] = useState(emptyFilter);
25✔
49

50
  const dispatch = useDispatch();
25✔
51
  const { hasFullFiltering, plan } = useSelector(getTenantCapabilities);
25✔
52
  const { groupFilters, selectedGroup } = useSelector(getSelectedGroupInfo);
25✔
53
  const attributes = useSelector(getFilterAttributes);
25✔
54
  const filters = useSelector(getDeviceFilters);
25✔
55
  const isEnterprise = useSelector(getIsEnterprise);
25✔
56
  const { previousFilters = [] } = useSelector(getGlobalSettings);
25!
57

58
  useEffect(() => {
25✔
59
    if (open) {
4✔
60
      dispatch(getDeviceAttributes());
1✔
61
    }
62
  }, [dispatch, open]);
63

64
  const saveUpdatedFilter = useCallback(
25✔
65
    updatedFilter => {
66
      if (!previousFilters.find(filter => deepCompare(filter, updatedFilter))) {
×
67
        const changedPreviousFilters = [...previousFilters, updatedFilter];
×
68
        dispatch(saveGlobalSettings({ previousFilters: changedPreviousFilters.slice(-1 * MAX_PREVIOUS_FILTERS_COUNT) }));
×
69
      }
70
    },
71
    [dispatch, previousFilters]
72
  );
73

74
  const handleFilterChange = useCallback(
25✔
75
    filters => {
76
      const activeFilters = filters.filter(filtersFilter).filter(item => item.value !== '');
×
77
      dispatch(setDeviceFilters(activeFilters));
×
78
      dispatch(setDeviceListState({ selectedId: undefined, page: 1 }, true, true));
×
79
    },
80
    [dispatch]
81
  );
82

83
  const updateFilter = useCallback(
25✔
84
    updatedFilter => {
85
      saveUpdatedFilter(updatedFilter);
×
86
      handleFilterChange([...filters, updatedFilter]);
×
87
      setReset(toggle);
×
88
    },
89
    [filters, handleFilterChange, saveUpdatedFilter]
90
  );
91

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

94
  const removeFilter = removedFilter => {
25✔
95
    if (removedFilter.key === 'id') {
×
96
      resetIdFilter();
×
97
    }
98
    let changedFilters = filters.filter(filter => !deepCompare(filter, removedFilter));
×
99
    handleFilterChange(changedFilters);
×
100
  };
101

102
  const clearFilters = () => {
25✔
103
    handleFilterChange([]);
×
104
    resetIdFilter();
×
105
    setReset(toggle);
×
106
  };
107

108
  const onAddClick = () => {
25✔
109
    updateFilter(newFilter);
×
110
    setReset(toggle);
×
111
  };
112

113
  const isFilterDefined = Object.values(newFilter).every(thing => !!thing);
25✔
114
  const currentFilters = [...groupFilters, ...filters].filter(filtersFilter);
25✔
115
  return (
25✔
116
    <Collapse in={open} timeout="auto" className={`${className} filter-wrapper`} unmountOnExit>
117
      <>
118
        <div className="flexbox">
119
          <div className="margin-right" style={{ marginTop: currentFilters.length ? 8 : 25 }}>
25!
120
            Devices matching:
121
          </div>
122
          <div>
123
            <div className="filter-list">
124
              {currentFilters.map(item => (
125
                <Chip
×
126
                  className="margin-right-small"
127
                  key={`filter-${item.key}-${item.operator}-${item.value}`}
128
                  label={`${getFilterLabelByKey(item.key, attributes)} ${DEVICE_FILTERING_OPTIONS[item.operator].shortform} ${
129
                    item.operator !== DEVICE_FILTERING_OPTIONS.$exists.key && item.operator !== DEVICE_FILTERING_OPTIONS.$nexists.key
×
130
                      ? item.operator === DEVICE_FILTERING_OPTIONS.$regex.key
×
131
                        ? `${item.value}.*`
132
                        : item.value
133
                      : ''
134
                  }`}
135
                  onDelete={() => removeFilter(item)}
×
136
                />
137
              ))}
138
            </div>
139
            {(hasFullFiltering || !currentFilters.length) && (
58✔
140
              <>
141
                <FilterItem attributes={attributes} onChange={setNewFilter} onSelect={updateFilter} plan={plan} reset={reset} />
142
                {isFilterDefined && <Chip className="margin-bottom-small" icon={<AddIcon />} label="Add a rule" color="primary" onClick={onAddClick} />}
25!
143
              </>
144
            )}
145
            <EnterpriseNotification id={BENEFITS.fullFiltering.id} />
146
          </div>
147
        </div>
148
        {!!filters.length && !groupFilters.length && (
25!
149
          <div className="flexbox column margin-top-small margin-bottom-small" style={{ alignItems: 'flex-end' }}>
150
            <span className="link margin-small margin-top-none" onClick={clearFilters}>
151
              Clear filter
152
            </span>
153
          </div>
154
        )}
155
        {isEnterprise && !!filters.length && (
42!
156
          <div>
157
            {selectedGroup ? (
×
158
              !!groupFilters.length && (
×
159
                <MenderTooltip
160
                  title="Saved changes will not change the target devices of any ongoing deployments to this group, but will take effect for new deployments"
161
                  arrow
162
                >
163
                  <Button variant="contained" color="secondary" onClick={onGroupClick}>
164
                    Save group
165
                  </Button>
166
                </MenderTooltip>
167
              )
168
            ) : (
169
              <Button variant="contained" color="secondary" onClick={onGroupClick}>
170
                Create group with this filter
171
              </Button>
172
            )}
173
            <InfoHintContainer>
174
              <EnterpriseNotification id={BENEFITS.dynamicGroups.id} />
175
            </InfoHintContainer>
176
          </div>
177
        )}
178
      </>
179
    </Collapse>
180
  );
181
};
182

183
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