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

mendersoftware / gui / 1350829378

27 Jun 2024 01:46PM UTC coverage: 83.494% (-16.5%) from 99.965%
1350829378

Pull #4465

gitlab-ci

mzedel
chore: test fixes

Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #4465: MEN-7169 - feat: added multi sorting capabilities to devices view

4506 of 6430 branches covered (70.08%)

81 of 100 new or added lines in 14 files covered. (81.0%)

1661 existing lines in 163 files now uncovered.

8574 of 10269 relevant lines covered (83.49%)

160.6 hits per line

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

81.03
/src/js/components/devices/widgets/attribute-autocomplete.js
1
// Copyright 2022 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, useRef, useState } from 'react';
15

16
// material ui
17
import { Autocomplete, TextField, createFilterOptions } from '@mui/material';
18

19
import { TIMEOUTS } from '../../../constants/appConstants';
20
import { emptyFilter } from '../../../constants/deviceConstants';
21
import { defaultHeaders } from '../base-devices';
22
import { getFilterLabelByKey } from './filters';
23

24
const textFieldStyle = { marginTop: 0, marginBottom: 15 };
184✔
25

26
export const getOptionLabel = option => {
184✔
27
  const header = Object.values(defaultHeaders).find(({ attribute }) => attribute.scope === option.scope && attribute.name === option.key);
699✔
28
  return header?.title || option.title || option.value || option.key || option;
118✔
29
};
30

31
const FilterOption = (props, option) => {
184✔
32
  let content = getOptionLabel(option);
20✔
33
  if (option.category === 'recently used') {
20!
UNCOV
34
    content = (
×
35
      <div className="flexbox center-aligned space-between" style={{ width: '100%' }}>
36
        <div>{content}</div>
37
        <div className="muted slightly-smaller">({option.scope})</div>
38
      </div>
39
    );
40
  }
41
  return <li {...props}>{content}</li>;
20✔
42
};
43

44
const optionsFilter = createFilterOptions();
184✔
45

46
const filterOptions = (options, params) => {
184✔
47
  const filtered = optionsFilter(options, params);
9✔
48
  if (filtered.length !== 1 && params.inputValue !== '') {
9✔
49
    filtered.push({
6✔
50
      inputValue: params.inputValue,
51
      key: 'custom',
52
      value: `Use "${params.inputValue}"`,
53
      category: 'custom',
54
      priority: 99
55
    });
56
  }
57
  return filtered;
9✔
58
};
59

60
export const AttributeAutoComplete = ({ attributes, disabled = false, filter = emptyFilter, label = 'Attribute', onRemove, onSelect, ...remainder }) => {
184!
61
  const [key, setKey] = useState(filter.key); // this refers to the selected filter with key as the id
16✔
62
  const [options, setOptions] = useState([]);
16✔
63
  const [reset, setReset] = useState(true);
16✔
64
  const [scope, setScope] = useState(filter.scope);
16✔
65
  const timer = useRef();
16✔
66

67
  useEffect(() => {
16✔
68
    return () => {
4✔
69
      clearTimeout(timer.current);
4✔
70
    };
71
  }, []);
72

73
  useEffect(() => {
16✔
74
    setKey(emptyFilter.key);
6✔
75
    setScope(emptyFilter.scope);
6✔
76
    let attributesClean = attributes.map(attr => {
6✔
77
      if (!attr.category && attr.scope) {
16!
UNCOV
78
        attr.category = attr.scope;
×
79
      }
80
      return attr;
16✔
81
    });
82
    setOptions(
6✔
83
      attributesClean.sort((a, b) =>
84
        a.category == b.category
26✔
85
          ? a.priority == b.priority
6!
86
            ? (a.key || '').localeCompare(b.key || '', { sensitivity: 'case' })
12!
87
            : a.priority - b.priority
88
          : (a.category || '').localeCompare(b.category || '', { sensitivity: 'case' })
40!
89
      )
90
    );
91
    // eslint-disable-next-line react-hooks/exhaustive-deps
92
  }, [attributes.length, reset]);
93

94
  useEffect(() => {
16✔
95
    setKey(filter.key);
4✔
96
  }, [filter.key]);
97

98
  useEffect(() => {
16✔
99
    clearTimeout(timer.current);
8✔
100
    timer.current = setTimeout(() => onSelect({ key, scope }), TIMEOUTS.debounceDefault);
8✔
101
    return () => {
8✔
102
      clearTimeout(timer.current);
8✔
103
    };
104
  }, [key, onSelect, scope]);
105

106
  const updateFilterKey = (value, selectedScope) => {
16✔
UNCOV
107
    if (!value) {
×
UNCOV
108
      return removeFilter();
×
109
    }
UNCOV
110
    const { key = value, scope: fallbackScope } = attributes.find(filter => filter.key === value) ?? {};
×
UNCOV
111
    setKey(key);
×
UNCOV
112
    setScope(selectedScope || fallbackScope);
×
113
  };
114

115
  const removeFilter = useCallback(() => {
16✔
UNCOV
116
    if (key) {
×
UNCOV
117
      onRemove({ key, scope });
×
118
    }
UNCOV
119
    setReset(!reset);
×
120
  }, [key, onRemove, reset, setReset, scope]);
121

122
  return (
16✔
123
    <Autocomplete
124
      {...remainder}
125
      autoComplete
126
      autoHighlight
127
      autoSelect
128
      disabled={disabled}
129
      freeSolo
130
      filterSelectedOptions
131
      filterOptions={filterOptions}
132
      getOptionLabel={getOptionLabel}
133
      groupBy={option => option.category}
20✔
134
      renderOption={FilterOption}
135
      id="filter-selection"
136
      includeInputInList={true}
137
      onChange={(e, changedValue) => {
138
        const { inputValue, key = changedValue, scope } = changedValue || {};
1!
139
        if (inputValue) {
1!
140
          // only circumvent updateFilterKey if we deal with a custom attribute - those will be treated as inventory attributes
141
          setKey(inputValue);
1✔
142
          return setScope(emptyFilter.scope);
1✔
143
        }
UNCOV
144
        updateFilterKey(key, scope);
×
145
      }}
146
      options={options}
147
      renderInput={params => <TextField {...params} label={label} style={textFieldStyle} />}
30✔
148
      key={reset}
149
      value={getFilterLabelByKey(key, attributes)}
150
    />
151
  );
152
};
153

154
export default AttributeAutoComplete;
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