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

mendersoftware / mender-server / 10423

11 Nov 2025 04:53PM UTC coverage: 74.435% (-0.1%) from 74.562%
10423

push

gitlab-ci

web-flow
Merge pull request #1071 from mendersoftware/dependabot/npm_and_yarn/frontend/main/development-dependencies-92732187be

3868 of 5393 branches covered (71.72%)

Branch coverage included in aggregate %.

5 of 5 new or added lines in 2 files covered. (100.0%)

176 existing lines in 95 files now uncovered.

64605 of 86597 relevant lines covered (74.6%)

7.74 hits per line

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

96.67
/frontend/src/js/common-ui/Search.tsx
1
// Copyright 2022 Northern.tech AS
2✔
2
//
2✔
3
//    Licensed under the Apache License, Version 2.0 (the "License");
2✔
4
//    you may not use this file except in compliance with the License.
2✔
5
//    You may obtain a copy of the License at
2✔
6
//
2✔
7
//        http://www.apache.org/licenses/LICENSE-2.0
2✔
8
//
2✔
9
//    Unless required by applicable law or agreed to in writing, software
2✔
10
//    distributed under the License is distributed on an "AS IS" BASIS,
2✔
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2✔
12
//    See the License for the specific language governing permissions and
2✔
13
//    limitations under the License.
2✔
14
import { useCallback, useEffect, useRef } from 'react';
2✔
15
import { Controller, FormProvider, useForm, useFormContext } from 'react-hook-form';
2✔
16

2✔
17
import { Search as SearchIcon } from '@mui/icons-material';
2✔
18
import { InputAdornment, TextField } from '@mui/material';
2✔
19

2✔
20
import { TIMEOUTS } from '@northern.tech/store/constants';
2✔
21
import { useDebounce } from '@northern.tech/utils/debouncehook';
2✔
22

2✔
23
import Loader from './Loader';
2✔
24

2✔
25
const endAdornment = (
2✔
26
  <InputAdornment position="end">
8✔
27
    <Loader show small style={{ marginTop: -10 }} />
2✔
28
  </InputAdornment>
2✔
29
);
2✔
30

2✔
31
const startAdornment = (
2✔
32
  <InputAdornment position="start">
8✔
33
    <SearchIcon color="disabled" fontSize="small" />
2✔
34
  </InputAdornment>
2✔
35
);
2✔
36

2✔
37
// due to search not working reliably for single letter searches, only start at 2
2✔
38
const MINIMUM_SEARCH_LENGTH = 2;
8✔
39

2✔
40
export const ControlledSearch = ({ className = '', isSearching, name = 'search', onSearch, placeholder = 'Search devices', style = {} }) => {
8✔
41
  const { control, watch } = useFormContext();
162✔
42
  const inputRef = useRef();
162✔
43
  const focusLockRef = useRef(true);
162✔
44
  const timer = useRef(); // this + the above focusLock are needed to work around the focus being reassigned to the input field which would cause runaway search triggers
162✔
45
  const triggerDebounceRef = useRef(false); // this is needed to reject the search triggered through the recreation of the onSearch callback
162✔
46

2✔
47
  const searchValue = watch(name, '');
162✔
48

2✔
49
  const debouncedSearchTerm = useDebounce(searchValue, TIMEOUTS.debounceDefault);
162✔
50

2✔
51
  const focusAndLock = () => {
162✔
52
    focusLockRef.current = false;
3✔
53
    inputRef.current.focus();
3✔
54
    clearTimeout(timer.current);
3✔
55
    triggerDebounceRef.current = false;
3✔
56
    timer.current = setTimeout(() => (focusLockRef.current = true), TIMEOUTS.oneSecond);
3✔
57
  };
2✔
58

2✔
59
  useEffect(
162✔
60
    () => () => {
13✔
61
      clearTimeout(timer.current);
13✔
62
    },
2✔
63
    []
2✔
64
  );
2✔
65

2✔
66
  useEffect(() => {
162✔
67
    if (debouncedSearchTerm.length < MINIMUM_SEARCH_LENGTH || triggerDebounceRef.current) {
14✔
68
      return;
13✔
69
    }
2✔
70
    triggerDebounceRef.current = true;
3✔
71
    onSearch(debouncedSearchTerm).then(focusAndLock);
3✔
72
  }, [debouncedSearchTerm, onSearch]);
2✔
73

2✔
74
  const onTriggerSearch = useCallback(
162✔
75
    ({ key }) => {
2✔
76
      if (key === 'Enter' && (!debouncedSearchTerm || debouncedSearchTerm.length >= MINIMUM_SEARCH_LENGTH)) {
4!
77
        onSearch(debouncedSearchTerm).then(focusAndLock);
2✔
78
      }
2✔
79
    },
2✔
80
    [debouncedSearchTerm, onSearch]
2✔
81
  );
2✔
82

2✔
83
  const onFocus = useCallback(() => {
162✔
84
    if (focusLockRef.current && debouncedSearchTerm.length >= MINIMUM_SEARCH_LENGTH) {
3!
85
      onSearch(debouncedSearchTerm).then(focusAndLock);
2✔
86
    }
2✔
87
  }, [debouncedSearchTerm, onSearch]);
2✔
88

2✔
89
  const adornments = isSearching ? { startAdornment, endAdornment } : { startAdornment };
162✔
90
  return (
162✔
91
    <Controller
2✔
92
      name={name}
2✔
93
      control={control}
2✔
94
      render={({ field }) => (
2✔
95
        <TextField
159✔
96
          className={className}
2✔
97
          slotProps={{ input: adornments }}
2✔
98
          onKeyUp={onTriggerSearch}
2✔
99
          onFocus={onFocus}
2✔
100
          placeholder={placeholder}
2✔
101
          inputRef={inputRef}
2✔
102
          size="small"
2✔
103
          style={style}
2✔
104
          {...field}
2✔
105
        />
2✔
106
      )}
2✔
107
    />
2✔
108
  );
2✔
109
};
2✔
110

2✔
111
ControlledSearch.displayName = 'ConnectedSearch';
8✔
112

2✔
113
const Search = props => {
8✔
114
  const { className = '', searchTerm, onSearch, trigger } = props;
86✔
115
  const methods = useForm({ mode: 'onChange', defaultValues: { search: searchTerm ?? '' } });
86!
116
  const { handleSubmit } = methods;
86✔
117
  const onSubmit = useCallback(search => onSearch(search, !trigger), [onSearch, trigger]);
86✔
118
  return (
86✔
119
    <FormProvider {...methods}>
2✔
UNCOV
120
      <form className={className} noValidate onSubmit={handleSubmit(({ search }) => onSearch(search, !trigger))}>
2✔
121
        <ControlledSearch {...props} onSearch={onSubmit} />
2✔
122
        <input className="hidden" type="submit" />
2✔
123
      </form>
2✔
124
    </FormProvider>
2✔
125
  );
2✔
126
};
2✔
127

2✔
128
export default Search;
2✔
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