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

mendersoftware / gui / 1113439055

19 Dec 2023 09:01PM UTC coverage: 82.752% (-17.2%) from 99.964%
1113439055

Pull #4258

gitlab-ci

mender-test-bot
chore: Types update

Signed-off-by: Mender Test Bot <mender@northern.tech>
Pull Request #4258: chore: Types update

4326 of 6319 branches covered (0.0%)

8348 of 10088 relevant lines covered (82.75%)

189.39 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 FilterItem from './filteritem';
30

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

36
const MAX_PREVIOUS_FILTERS_COUNT = 3;
183✔
37

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

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

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

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

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

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

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

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

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

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

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

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

112
  const isFilterDefined = Object.values(newFilter).every(thing => !!thing);
25✔
113
  const currentFilters = [...groupFilters, ...filters].filter(filtersFilter);
25✔
114
  return (
25✔
115
    <Collapse in={open} timeout="auto" className={`${className} filter-wrapper`} unmountOnExit>
116
      <>
117
        <div className="flexbox">
118
          <div className="margin-right" style={{ marginTop: currentFilters.length ? 8 : 25 }}>
25!
119
            Devices matching:
120
          </div>
121
          <div>
122
            <div className="filter-list">
123
              {currentFilters.map(item => (
124
                <Chip
×
125
                  className="margin-right-small"
126
                  key={`filter-${item.key}-${item.operator}-${item.value}`}
127
                  title={item.isGroupFilter ? 'Group definition filter' : ''}
×
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={item.isGroupFilter ? undefined : () => 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
              <Button variant="contained" color="secondary" onClick={onGroupClick}>
159
                Create group with this filter
160
              </Button>
161
            )}
162
            <InfoHintContainer>
163
              <EnterpriseNotification id={BENEFITS.dynamicGroups.id} />
164
            </InfoHintContainer>
165
          </div>
166
        )}
167
      </>
168
    </Collapse>
169
  );
170
};
171

172
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