katello 4.8.0.rc2 → 4.8.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of katello might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/katello/plugin.rb +0 -12
- data/lib/katello/version.rb +1 -1
- data/webpack/components/Content/{ContentPage.js → GenericContentPage.js} +7 -4
- data/webpack/components/Content/__tests__/ContentTable.test.js +1 -1
- data/webpack/components/Content/__tests__/GenericContentPage.test.js +35 -0
- data/webpack/components/Search/SearchText.js +70 -0
- data/webpack/components/Table/EmptyStateMessage.js +2 -2
- data/webpack/components/Table/TableWrapper.js +4 -0
- data/webpack/components/extensions/HostDetails/HostDetailsConstants.js +0 -1
- data/webpack/components/extensions/HostDetails/HostDetailsSelectors.js +0 -6
- data/webpack/components/extensions/HostDetails/Tabs/__tests__/moduleStreamsTab.test.js +76 -0
- data/webpack/components/extensions/SearchBar/SearchBarConstants.js +3 -0
- data/webpack/components/extensions/SearchBar/SearchBarHooks.js +50 -0
- data/webpack/components/extensions/SearchBar/SearchBarReducer.js +14 -0
- data/webpack/components/extensions/SearchBar/SearchBarSelectors.js +5 -0
- data/webpack/redux/reducers/index.js +2 -2
- data/webpack/scenes/AlternateContentSources/Create/__tests__/acsCreate.test.js +1 -13
- data/webpack/scenes/AlternateContentSources/MainTable/ACSTable.js +1 -0
- data/webpack/scenes/Content/{ContentPage.js → GenericContentPage.js} +2 -2
- data/webpack/scenes/Content/__tests__/contentTable.test.js +2 -2
- data/webpack/scenes/Content/index.js +2 -2
- data/webpack/scenes/ContentViews/Details/Filters/Rules/ContainerTag/AddEditContainerTagRuleModal.js +14 -17
- data/webpack/scenes/ContentViews/Details/Filters/Rules/Package/AddEditPackageRuleModal.js +24 -28
- data/webpack/scenes/ContentViews/Details/Filters/__tests__/CVContainerImageFilterContent.test.js +11 -18
- data/webpack/scenes/ContentViews/Details/Filters/__tests__/CVRpmFilterContent.test.js +10 -23
- data/webpack/scenes/ContentViews/Details/Filters/__tests__/ContentViewPackageGroupFilter.test.js +0 -2
- data/webpack/scenes/ContentViews/Details/Versions/__tests__/contentViewVersions.test.js +1 -7
- data/webpack/scenes/ContentViews/expansions/__tests__/contentViewComponentsModal.test.js +0 -2
- data/webpack/scenes/ModuleStreams/ModuleStreamsPage.js +2 -2
- data/webpack/scenes/ModuleStreams/__tests__/ModuleStreamPage.test.js +2 -2
- data/webpack/scenes/ModuleStreams/__tests__/__snapshots__/ModuleStreamPage.test.js.snap +1 -1
- data/webpack/scenes/Settings/SettingsConstants.js +2 -3
- data/webpack/scenes/Settings/SettingsReducer.js +2 -16
- data/webpack/scenes/Settings/SettingsSelectors.js +2 -2
- data/webpack/test-utils/react-testing-lib-wrapper.js +0 -6
- metadata +12 -24
- data/webpack/components/Content/__tests__/ContentPage.test.js +0 -32
- data/webpack/components/Content/__tests__/__snapshots__/ContentPage.test.js.snap +0 -89
- data/webpack/components/Search/Search.js +0 -156
- data/webpack/components/Search/__tests__/search.test.js +0 -104
- data/webpack/components/Search/helpers.js +0 -6
- data/webpack/components/Search/index.js +0 -15
- data/webpack/components/TypeAhead/TypeAhead.js +0 -157
- data/webpack/components/TypeAhead/TypeAhead.scss +0 -7
- data/webpack/components/TypeAhead/helpers/commonPropTypes.js +0 -35
- data/webpack/components/TypeAhead/helpers/helpers.js +0 -32
- data/webpack/components/TypeAhead/index.js +0 -3
- data/webpack/components/TypeAhead/pf3Search/TypeAheadInput.js +0 -44
- data/webpack/components/TypeAhead/pf3Search/TypeAheadItems.js +0 -56
- data/webpack/components/TypeAhead/pf3Search/TypeAheadSearch.js +0 -53
- data/webpack/components/TypeAhead/pf4Search/TypeAheadInput.js +0 -66
- data/webpack/components/TypeAhead/pf4Search/TypeAheadInput.scss +0 -12
- data/webpack/components/TypeAhead/pf4Search/TypeAheadItems.js +0 -59
- data/webpack/components/TypeAhead/pf4Search/TypeAheadSearch.js +0 -81
@@ -1,89 +0,0 @@
|
|
1
|
-
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
2
|
-
|
3
|
-
exports[`Content page should render and contain appropriate components 1`] = `
|
4
|
-
<Grid
|
5
|
-
bsClass="container-fluid"
|
6
|
-
componentClass="div"
|
7
|
-
fluid={false}
|
8
|
-
>
|
9
|
-
<Row
|
10
|
-
bsClass="row"
|
11
|
-
componentClass="div"
|
12
|
-
>
|
13
|
-
<Col
|
14
|
-
bsClass="col"
|
15
|
-
componentClass="div"
|
16
|
-
sm={12}
|
17
|
-
>
|
18
|
-
<h1>
|
19
|
-
Content Header
|
20
|
-
</h1>
|
21
|
-
</Col>
|
22
|
-
</Row>
|
23
|
-
<Row
|
24
|
-
bsClass="row"
|
25
|
-
componentClass="div"
|
26
|
-
>
|
27
|
-
<Col
|
28
|
-
bsClass="col"
|
29
|
-
componentClass="div"
|
30
|
-
sm={6}
|
31
|
-
>
|
32
|
-
<Form
|
33
|
-
bsClass="form"
|
34
|
-
className="toolbar-pf-actions"
|
35
|
-
componentClass="form"
|
36
|
-
horizontal={false}
|
37
|
-
inline={false}
|
38
|
-
>
|
39
|
-
<FormGroup
|
40
|
-
bsClass="form-group"
|
41
|
-
className="toolbar-pf toolbar-pf-filter"
|
42
|
-
>
|
43
|
-
<SearchBar
|
44
|
-
data={
|
45
|
-
Object {
|
46
|
-
"autocomplete": Object {
|
47
|
-
"apiParams": Object {},
|
48
|
-
"id": "searchBar-content-page-Content Header",
|
49
|
-
"searchQuery": "",
|
50
|
-
"url": "undefined/auto_complete_search",
|
51
|
-
"useKeyShortcuts": true,
|
52
|
-
},
|
53
|
-
"bookmarks": Object {
|
54
|
-
"canCreate": true,
|
55
|
-
"documentationUrl": "/links/manual/4.1.5Searching",
|
56
|
-
"id": "searchBar-content-page-Content Header",
|
57
|
-
"url": "/api/bookmarks",
|
58
|
-
},
|
59
|
-
"controller": undefined,
|
60
|
-
}
|
61
|
-
}
|
62
|
-
initialInputValue=""
|
63
|
-
initialQuery=""
|
64
|
-
name={null}
|
65
|
-
onSearch={[Function]}
|
66
|
-
onSearchChange={[Function]}
|
67
|
-
/>
|
68
|
-
</FormGroup>
|
69
|
-
</Form>
|
70
|
-
</Col>
|
71
|
-
</Row>
|
72
|
-
<Row
|
73
|
-
bsClass="row"
|
74
|
-
componentClass="div"
|
75
|
-
>
|
76
|
-
<Col
|
77
|
-
bsClass="col"
|
78
|
-
componentClass="div"
|
79
|
-
sm={12}
|
80
|
-
>
|
81
|
-
<ContentTable
|
82
|
-
content={Object {}}
|
83
|
-
onPaginationChange={[Function]}
|
84
|
-
tableSchema={Array []}
|
85
|
-
/>
|
86
|
-
</Col>
|
87
|
-
</Row>
|
88
|
-
</Grid>
|
89
|
-
`;
|
@@ -1,156 +0,0 @@
|
|
1
|
-
import React, { useState, useEffect, useRef } from 'react';
|
2
|
-
import { useDispatch } from 'react-redux';
|
3
|
-
import { ControlLabel } from 'react-bootstrap';
|
4
|
-
import { loadSetting } from 'foremanReact/components/Settings/SettingsActions';
|
5
|
-
import { translate as __ } from 'foremanReact/common/I18n';
|
6
|
-
import PropTypes from 'prop-types';
|
7
|
-
import TypeAhead from '../TypeAhead';
|
8
|
-
import api, { foremanApi } from '../../services/api';
|
9
|
-
import { stringIncludes } from './helpers';
|
10
|
-
import {
|
11
|
-
AUTOSEARCH_DELAY,
|
12
|
-
AUTOSEARCH_WHILE_TYPING,
|
13
|
-
} from '../../scenes/Settings/SettingsConstants';
|
14
|
-
|
15
|
-
// Don't trigger auto-search until you've typed an operator and begun to type a value
|
16
|
-
const searchContainsOperator = (search = '') => {
|
17
|
-
const operators = ['>=', '<=', '>', '<', '~', '^', '!^', '=', '!=', '~', '!~'];
|
18
|
-
return operators.some(operator => search.includes(operator) && !search.trim().endsWith(operator));
|
19
|
-
};
|
20
|
-
// Apply the above function only to the last 'key = value' pair, if there are multiple
|
21
|
-
const allowSearch = (originalSearch = '') => {
|
22
|
-
const search = originalSearch.toLowerCase().trim();
|
23
|
-
// find the last occurrence of 'or' or 'and'
|
24
|
-
const lastOr = originalSearch.lastIndexOf(' or ');
|
25
|
-
const lastAnd = originalSearch.lastIndexOf(' and ');
|
26
|
-
if (lastOr === -1 && lastAnd === -1) {
|
27
|
-
return searchContainsOperator(search);
|
28
|
-
}
|
29
|
-
const winningOperator = lastOr > lastAnd ? ' or ' : ' and ';
|
30
|
-
const lastSearch = search.split(winningOperator);
|
31
|
-
return searchContainsOperator(lastSearch[lastSearch.length - 1]);
|
32
|
-
};
|
33
|
-
|
34
|
-
const Search = ({
|
35
|
-
onSearch,
|
36
|
-
updateSearchQuery,
|
37
|
-
isDisabled,
|
38
|
-
settings: { autoSearchDelay, autoSearchEnabled = true },
|
39
|
-
initialInputValue,
|
40
|
-
patternfly4,
|
41
|
-
getAutoCompleteParams,
|
42
|
-
foremanApiAutoComplete,
|
43
|
-
bookmarkController,
|
44
|
-
readOnlyBookmarks,
|
45
|
-
placeholder,
|
46
|
-
isTextInput,
|
47
|
-
setTextInputValue,
|
48
|
-
}) => {
|
49
|
-
const [items, setItems] = useState([]);
|
50
|
-
const dispatch = useDispatch();
|
51
|
-
const mountedRef = useRef(true);
|
52
|
-
const onInputUpdate = async (searchTerm = '') => {
|
53
|
-
const newItems = items.filter(({ text }) => stringIncludes(text, searchTerm));
|
54
|
-
|
55
|
-
if (newItems.length !== items.length) {
|
56
|
-
// Checking whether the current component is mounted before state change events
|
57
|
-
if (!mountedRef.current) return;
|
58
|
-
setItems(newItems);
|
59
|
-
}
|
60
|
-
|
61
|
-
const { params, endpoint } = getAutoCompleteParams(searchTerm);
|
62
|
-
|
63
|
-
if (endpoint) {
|
64
|
-
let data;
|
65
|
-
if (foremanApiAutoComplete) {
|
66
|
-
data = await foremanApi.get(endpoint, undefined, params);
|
67
|
-
} else {
|
68
|
-
data = await api.get(endpoint, undefined, params);
|
69
|
-
}
|
70
|
-
// Checking whether the current component is mounted before state change events
|
71
|
-
if (!mountedRef.current) return;
|
72
|
-
switch (true) {
|
73
|
-
case endpoint.includes('auto_complete_arch'):
|
74
|
-
case endpoint.includes('auto_complete_name'):
|
75
|
-
setItems(data?.data?.map(label => ({ text: label.trim() })));
|
76
|
-
break;
|
77
|
-
default:
|
78
|
-
setItems(data?.data?.filter(({ error }) => !error).map(({ label }) => ({
|
79
|
-
text: label.trim(),
|
80
|
-
})));
|
81
|
-
}
|
82
|
-
}
|
83
|
-
if (autoSearchEnabled && patternfly4 && allowSearch(searchTerm)) {
|
84
|
-
onSearch(searchTerm || '');
|
85
|
-
}
|
86
|
-
};
|
87
|
-
|
88
|
-
useEffect(() => {
|
89
|
-
dispatch(loadSetting(AUTOSEARCH_DELAY));
|
90
|
-
dispatch(loadSetting(AUTOSEARCH_WHILE_TYPING));
|
91
|
-
return () => { mountedRef.current = false; };
|
92
|
-
}, [dispatch]);
|
93
|
-
|
94
|
-
const onNewSearch = (search) => {
|
95
|
-
if (updateSearchQuery) updateSearchQuery(search);
|
96
|
-
onSearch(search);
|
97
|
-
};
|
98
|
-
|
99
|
-
return (
|
100
|
-
<div>
|
101
|
-
<ControlLabel srOnly>{__('Search')}</ControlLabel>
|
102
|
-
<TypeAhead
|
103
|
-
autoSearchDelay={autoSearchDelay}
|
104
|
-
bookmarkController={bookmarkController}
|
105
|
-
readOnlyBookmarks={readOnlyBookmarks}
|
106
|
-
isDisabled={isDisabled}
|
107
|
-
items={items}
|
108
|
-
onInputUpdate={onInputUpdate}
|
109
|
-
onSearch={onNewSearch}
|
110
|
-
initialInputValue={initialInputValue}
|
111
|
-
patternfly4={patternfly4}
|
112
|
-
autoSearchEnabled={autoSearchEnabled}
|
113
|
-
placeholder={placeholder}
|
114
|
-
isTextInput={isTextInput}
|
115
|
-
setTextInputValue={setTextInputValue}
|
116
|
-
/>
|
117
|
-
</div>
|
118
|
-
);
|
119
|
-
};
|
120
|
-
|
121
|
-
Search.propTypes = {
|
122
|
-
onSearch: PropTypes.func.isRequired,
|
123
|
-
getAutoCompleteParams: PropTypes.func.isRequired,
|
124
|
-
foremanApiAutoComplete: PropTypes.bool,
|
125
|
-
updateSearchQuery: PropTypes.func,
|
126
|
-
initialInputValue: PropTypes.string,
|
127
|
-
patternfly4: PropTypes.bool,
|
128
|
-
isDisabled: PropTypes.bool,
|
129
|
-
settings: PropTypes.shape({
|
130
|
-
autoSearchEnabled: PropTypes.bool,
|
131
|
-
autoSearchDelay: PropTypes.number,
|
132
|
-
}),
|
133
|
-
bookmarkController: PropTypes.string,
|
134
|
-
readOnlyBookmarks: PropTypes.bool,
|
135
|
-
placeholder: PropTypes.string,
|
136
|
-
isTextInput: PropTypes.bool,
|
137
|
-
setTextInputValue: PropTypes.func,
|
138
|
-
};
|
139
|
-
|
140
|
-
Search.defaultProps = {
|
141
|
-
updateSearchQuery: undefined,
|
142
|
-
foremanApiAutoComplete: false,
|
143
|
-
initialInputValue: '',
|
144
|
-
patternfly4: false,
|
145
|
-
settings: {
|
146
|
-
autoSearchEnabled: true,
|
147
|
-
},
|
148
|
-
isDisabled: undefined,
|
149
|
-
bookmarkController: undefined,
|
150
|
-
readOnlyBookmarks: false,
|
151
|
-
placeholder: undefined,
|
152
|
-
isTextInput: false,
|
153
|
-
setTextInputValue: undefined,
|
154
|
-
};
|
155
|
-
|
156
|
-
export default Search;
|
@@ -1,104 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import { renderWithRedux, patientlyWaitFor, fireEvent } from 'react-testing-lib-wrapper';
|
3
|
-
import {
|
4
|
-
nockInstance, assertNockRequest, mockAutocomplete, mockSetting,
|
5
|
-
} from '../../../test-utils/nockWrapper';
|
6
|
-
import { AUTOSEARCH_WHILE_TYPING, AUTOSEARCH_DELAY } from '../../../scenes/Settings/SettingsConstants.js';
|
7
|
-
import Search from '../../Search';
|
8
|
-
|
9
|
-
const endpoint = '/fake_endpoint';
|
10
|
-
const searchButtonLabel = 'search button';
|
11
|
-
const props = {
|
12
|
-
onSearch: jest.fn(),
|
13
|
-
getAutoCompleteParams: search => ({
|
14
|
-
params: { organization_id: 1, search },
|
15
|
-
endpoint,
|
16
|
-
}),
|
17
|
-
patternfly4: true,
|
18
|
-
};
|
19
|
-
|
20
|
-
let searchDelayScope;
|
21
|
-
beforeEach(() => {
|
22
|
-
searchDelayScope = mockSetting(nockInstance, AUTOSEARCH_DELAY, 0);
|
23
|
-
});
|
24
|
-
|
25
|
-
afterEach(() => {
|
26
|
-
assertNockRequest(searchDelayScope);
|
27
|
-
});
|
28
|
-
|
29
|
-
jest.mock('../../../utils/useDebounce', () => ({
|
30
|
-
__esModule: true,
|
31
|
-
default: value => value,
|
32
|
-
}));
|
33
|
-
|
34
|
-
|
35
|
-
test('Autocomplete shows on input', async (done) => {
|
36
|
-
const suggestion = 'suggestedQuery';
|
37
|
-
const response = [
|
38
|
-
{
|
39
|
-
completed: '', part: ` ${suggestion} `, label: ` ${suggestion} `, category: '',
|
40
|
-
},
|
41
|
-
];
|
42
|
-
const query = { organization_id: 1, search: 'foo' };
|
43
|
-
const autoSearchScope = mockSetting(nockInstance, AUTOSEARCH_WHILE_TYPING, true);
|
44
|
-
const initialScope = mockAutocomplete(nockInstance, endpoint, { ...query, search: '' }, []);
|
45
|
-
const autocompleteScope = mockAutocomplete(nockInstance, endpoint, query, response);
|
46
|
-
|
47
|
-
const { getByLabelText, getByText, queryByText } = renderWithRedux(<Search {...props} />);
|
48
|
-
|
49
|
-
expect(queryByText(`${suggestion}`)).not.toBeInTheDocument();
|
50
|
-
|
51
|
-
fireEvent.change(getByLabelText(/text input for search/i), { target: { value: 'foo' } });
|
52
|
-
|
53
|
-
await patientlyWaitFor(() => expect(getByText(`${suggestion}`)).toBeInTheDocument());
|
54
|
-
|
55
|
-
assertNockRequest(initialScope);
|
56
|
-
assertNockRequest(autoSearchScope);
|
57
|
-
assertNockRequest(autocompleteScope, done);
|
58
|
-
});
|
59
|
-
|
60
|
-
test('autosearch turned off does show patternfly 4 search button', async (done) => {
|
61
|
-
const autoSearchScope = mockSetting(nockInstance, AUTOSEARCH_WHILE_TYPING, false);
|
62
|
-
const autocompleteScope = mockAutocomplete(nockInstance, endpoint);
|
63
|
-
|
64
|
-
const { getByLabelText } = renderWithRedux(<Search {...props} />);
|
65
|
-
|
66
|
-
// Using patientlyWaitFor as the autoSearch setting defaults to true,
|
67
|
-
// it won't be changed until http call
|
68
|
-
await patientlyWaitFor(() => expect(getByLabelText(searchButtonLabel)).toBeInTheDocument());
|
69
|
-
|
70
|
-
assertNockRequest(autoSearchScope);
|
71
|
-
assertNockRequest(autocompleteScope, done);
|
72
|
-
});
|
73
|
-
|
74
|
-
test('search function is called when search is typed into with autosearch', async (done) => {
|
75
|
-
const autoSearchScope = mockSetting(nockInstance, AUTOSEARCH_WHILE_TYPING);
|
76
|
-
const autocompleteScope = mockAutocomplete(nockInstance, endpoint, true, [], 2);
|
77
|
-
const mockSearch = jest.fn();
|
78
|
-
|
79
|
-
const { getByLabelText } = renderWithRedux(<Search {...{ ...props, onSearch: mockSearch }} />);
|
80
|
-
fireEvent.change(getByLabelText(/text input for search/i), { target: { value: 'name = foo' } });
|
81
|
-
await patientlyWaitFor(() => expect(mockSearch.mock.calls).toHaveLength(1));
|
82
|
-
|
83
|
-
assertNockRequest(autoSearchScope);
|
84
|
-
assertNockRequest(autocompleteScope, done);
|
85
|
-
});
|
86
|
-
|
87
|
-
test('search function is called by clicking search button without autosearch', async (done) => {
|
88
|
-
const autoSearchScope = mockSetting(nockInstance, AUTOSEARCH_WHILE_TYPING, false);
|
89
|
-
const autocompleteScope = mockAutocomplete(nockInstance, endpoint, true, [], 2);
|
90
|
-
const mockSearch = jest.fn();
|
91
|
-
|
92
|
-
const { getByLabelText } = renderWithRedux(<Search {...{ ...props, onSearch: mockSearch }} />);
|
93
|
-
|
94
|
-
fireEvent.change(getByLabelText(/text input for search/i), { target: { value: 'foo' } });
|
95
|
-
let searchButton;
|
96
|
-
await patientlyWaitFor(() => {
|
97
|
-
searchButton = getByLabelText(searchButtonLabel);
|
98
|
-
expect(searchButton).toBeInTheDocument();
|
99
|
-
});
|
100
|
-
searchButton.click();
|
101
|
-
expect(mockSearch.mock.calls).toHaveLength(1);
|
102
|
-
assertNockRequest(autoSearchScope);
|
103
|
-
assertNockRequest(autocompleteScope, done);
|
104
|
-
});
|
@@ -1,15 +0,0 @@
|
|
1
|
-
import { bindActionCreators } from 'redux';
|
2
|
-
import { connect } from 'react-redux';
|
3
|
-
import * as settingActions from 'foremanReact/components/Settings/SettingsActions';
|
4
|
-
import { selectSettings } from '../../scenes/Settings/SettingsSelectors';
|
5
|
-
import Search from './Search';
|
6
|
-
|
7
|
-
const mapStateToProps = state => ({
|
8
|
-
settings: selectSettings(state),
|
9
|
-
});
|
10
|
-
|
11
|
-
const actions = { ...settingActions };
|
12
|
-
|
13
|
-
const mapDispatchToProps = dispatch => bindActionCreators(actions, dispatch);
|
14
|
-
|
15
|
-
export default connect(mapStateToProps, mapDispatchToProps)(Search);
|
@@ -1,157 +0,0 @@
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
2
|
-
import Downshift from 'downshift';
|
3
|
-
import PropTypes from 'prop-types';
|
4
|
-
import { translate as __ } from 'foremanReact/common/I18n';
|
5
|
-
import { useDispatch, useSelector } from 'react-redux';
|
6
|
-
import TypeAheadSearch from './pf3Search/TypeAheadSearch';
|
7
|
-
// eslint-disable-next-line import/no-named-default
|
8
|
-
import { default as TypeAheadSearchPf4 } from './pf4Search/TypeAheadSearch';
|
9
|
-
import { getActiveItems } from './helpers/helpers';
|
10
|
-
import './TypeAhead.scss';
|
11
|
-
import useDebounce from '../../utils/useDebounce';
|
12
|
-
import { selectHostDetailsClearSearch } from '../extensions/HostDetails/HostDetailsSelectors';
|
13
|
-
|
14
|
-
|
15
|
-
const TypeAhead = ({
|
16
|
-
items,
|
17
|
-
isDisabled,
|
18
|
-
onInputUpdate,
|
19
|
-
onSearch,
|
20
|
-
actionText,
|
21
|
-
patternfly4,
|
22
|
-
autoSearchEnabled,
|
23
|
-
autoSearchDelay,
|
24
|
-
bookmarkController,
|
25
|
-
readOnlyBookmarks,
|
26
|
-
placeholder,
|
27
|
-
isTextInput,
|
28
|
-
setTextInputValue,
|
29
|
-
initialInputValue,
|
30
|
-
}) => {
|
31
|
-
const [inputValue, setInputValue] = useState(initialInputValue);
|
32
|
-
const debouncedValue = useDebounce(inputValue, autoSearchDelay);
|
33
|
-
const dispatch = useDispatch();
|
34
|
-
const existingClearSearch = useSelector(selectHostDetailsClearSearch);
|
35
|
-
useEffect(
|
36
|
-
() => {
|
37
|
-
onInputUpdate(debouncedValue);
|
38
|
-
},
|
39
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
40
|
-
[debouncedValue],
|
41
|
-
);
|
42
|
-
// eslint-disable-next-line arrow-body-style
|
43
|
-
useEffect(() => {
|
44
|
-
return function cleanupClearSearch() {
|
45
|
-
dispatch({
|
46
|
-
type: 'SET_CLEAR_SEARCH',
|
47
|
-
payload: {},
|
48
|
-
});
|
49
|
-
};
|
50
|
-
}, [dispatch]);
|
51
|
-
|
52
|
-
const clearSearch = () => {
|
53
|
-
setInputValue('');
|
54
|
-
onSearch('');
|
55
|
-
};
|
56
|
-
|
57
|
-
const handleStateChange = ({ inputValue: value }) => {
|
58
|
-
if (typeof value === 'string') {
|
59
|
-
setInputValue(value);
|
60
|
-
if (setTextInputValue) setTextInputValue(value);
|
61
|
-
}
|
62
|
-
if (typeof existingClearSearch !== 'function') {
|
63
|
-
dispatch({
|
64
|
-
type: 'SET_CLEAR_SEARCH',
|
65
|
-
payload: clearSearch,
|
66
|
-
});
|
67
|
-
}
|
68
|
-
};
|
69
|
-
const activeItems = getActiveItems(items);
|
70
|
-
return (
|
71
|
-
<Downshift
|
72
|
-
onStateChange={handleStateChange}
|
73
|
-
defaultHighlightedIndex={0}
|
74
|
-
selectedItem={inputValue}
|
75
|
-
>
|
76
|
-
{({
|
77
|
-
getInputProps,
|
78
|
-
getItemProps,
|
79
|
-
isOpen,
|
80
|
-
inputValue: internalInputValue,
|
81
|
-
highlightedIndex,
|
82
|
-
selectedItem,
|
83
|
-
selectItem,
|
84
|
-
openMenu,
|
85
|
-
}) => {
|
86
|
-
const typeAheadProps = {
|
87
|
-
bookmarkController,
|
88
|
-
readOnlyBookmarks,
|
89
|
-
isDisabled,
|
90
|
-
userInputValue: inputValue,
|
91
|
-
clearSearch,
|
92
|
-
getInputProps,
|
93
|
-
getItemProps,
|
94
|
-
isOpen,
|
95
|
-
inputValue: internalInputValue,
|
96
|
-
highlightedIndex,
|
97
|
-
selectedItem,
|
98
|
-
selectItem,
|
99
|
-
openMenu,
|
100
|
-
onSearch,
|
101
|
-
items,
|
102
|
-
activeItems,
|
103
|
-
placeholder,
|
104
|
-
shouldShowItems: isOpen && items.length > 0,
|
105
|
-
isTextInput,
|
106
|
-
};
|
107
|
-
|
108
|
-
return (
|
109
|
-
<div>
|
110
|
-
{patternfly4 ?
|
111
|
-
<TypeAheadSearchPf4 autoSearchEnabled={autoSearchEnabled} {...typeAheadProps} /> :
|
112
|
-
<TypeAheadSearch actionText={actionText} {...typeAheadProps} placeholder={null} />}
|
113
|
-
</div>
|
114
|
-
);
|
115
|
-
}}
|
116
|
-
</Downshift>
|
117
|
-
);
|
118
|
-
};
|
119
|
-
|
120
|
-
TypeAhead.propTypes = {
|
121
|
-
items: PropTypes.arrayOf(PropTypes.shape({
|
122
|
-
/* text to display in MenuItem */
|
123
|
-
text: PropTypes.string,
|
124
|
-
/* item can be a header or divider or undefined for regular item */
|
125
|
-
type: PropTypes.oneOf(['header', 'divider']),
|
126
|
-
/* optionally disable a regular item */
|
127
|
-
disabled: PropTypes.bool,
|
128
|
-
})).isRequired,
|
129
|
-
isDisabled: PropTypes.bool,
|
130
|
-
onInputUpdate: PropTypes.func.isRequired,
|
131
|
-
onSearch: PropTypes.func.isRequired,
|
132
|
-
actionText: PropTypes.string,
|
133
|
-
initialInputValue: PropTypes.string,
|
134
|
-
patternfly4: PropTypes.bool,
|
135
|
-
autoSearchEnabled: PropTypes.bool.isRequired,
|
136
|
-
autoSearchDelay: PropTypes.number,
|
137
|
-
bookmarkController: PropTypes.string,
|
138
|
-
readOnlyBookmarks: PropTypes.bool,
|
139
|
-
placeholder: PropTypes.string,
|
140
|
-
isTextInput: PropTypes.bool,
|
141
|
-
setTextInputValue: PropTypes.func,
|
142
|
-
};
|
143
|
-
|
144
|
-
TypeAhead.defaultProps = {
|
145
|
-
actionText: __('Search'),
|
146
|
-
initialInputValue: '',
|
147
|
-
patternfly4: false,
|
148
|
-
isDisabled: undefined,
|
149
|
-
autoSearchDelay: 500,
|
150
|
-
bookmarkController: undefined,
|
151
|
-
readOnlyBookmarks: false,
|
152
|
-
placeholder: undefined,
|
153
|
-
isTextInput: false,
|
154
|
-
setTextInputValue: undefined,
|
155
|
-
};
|
156
|
-
|
157
|
-
export default TypeAhead;
|
@@ -1,35 +0,0 @@
|
|
1
|
-
import PropTypes from 'prop-types';
|
2
|
-
|
3
|
-
const commonSearchPropTypes = {
|
4
|
-
userInputValue: PropTypes.string.isRequired,
|
5
|
-
clearSearch: PropTypes.func.isRequired,
|
6
|
-
getInputProps: PropTypes.func.isRequired,
|
7
|
-
getItemProps: PropTypes.func.isRequired,
|
8
|
-
isOpen: PropTypes.bool.isRequired,
|
9
|
-
inputValue: PropTypes.string.isRequired,
|
10
|
-
highlightedIndex: PropTypes.number.isRequired,
|
11
|
-
selectedItem: PropTypes.string.isRequired,
|
12
|
-
selectItem: PropTypes.func.isRequired,
|
13
|
-
openMenu: PropTypes.func.isRequired,
|
14
|
-
onSearch: PropTypes.func.isRequired,
|
15
|
-
items: PropTypes.arrayOf(PropTypes.shape({
|
16
|
-
text: PropTypes.string,
|
17
|
-
})).isRequired,
|
18
|
-
activeItems: PropTypes.arrayOf(PropTypes.string).isRequired,
|
19
|
-
shouldShowItems: PropTypes.bool.isRequired,
|
20
|
-
};
|
21
|
-
|
22
|
-
export const commonInputPropTypes = {
|
23
|
-
passedProps: PropTypes.shape({}).isRequired,
|
24
|
-
onKeyPress: PropTypes.func.isRequired,
|
25
|
-
onInputFocus: PropTypes.func.isRequired,
|
26
|
-
};
|
27
|
-
|
28
|
-
export const commonItemPropTypes = {
|
29
|
-
items: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
30
|
-
activeItems: PropTypes.arrayOf(PropTypes.string).isRequired,
|
31
|
-
highlightedIndex: PropTypes.number.isRequired,
|
32
|
-
getItemProps: PropTypes.func.isRequired,
|
33
|
-
};
|
34
|
-
|
35
|
-
export default commonSearchPropTypes;
|
@@ -1,32 +0,0 @@
|
|
1
|
-
import { KEYCODES } from 'foremanReact/common/keyCodes';
|
2
|
-
|
3
|
-
const keyPressHandler = (
|
4
|
-
e, isOpen, activeItems, highlightedIndex,
|
5
|
-
selectItem, userInputValue, onSearch,
|
6
|
-
) => {
|
7
|
-
switch (e.keyCode) {
|
8
|
-
case KEYCODES.TAB_KEY:
|
9
|
-
if (isOpen && activeItems[highlightedIndex]) {
|
10
|
-
selectItem(activeItems[highlightedIndex]);
|
11
|
-
e.preventDefault();
|
12
|
-
}
|
13
|
-
break;
|
14
|
-
|
15
|
-
case KEYCODES.ENTER:
|
16
|
-
if (!isOpen || !activeItems[highlightedIndex]) {
|
17
|
-
onSearch(userInputValue);
|
18
|
-
e.preventDefault();
|
19
|
-
}
|
20
|
-
break;
|
21
|
-
|
22
|
-
default:
|
23
|
-
break;
|
24
|
-
}
|
25
|
-
};
|
26
|
-
|
27
|
-
export const getActiveItems = items =>
|
28
|
-
items
|
29
|
-
.filter(({ disabled, type }) => !disabled && !['header', 'divider'].includes(type))
|
30
|
-
.map(({ text }) => text);
|
31
|
-
|
32
|
-
export default keyPressHandler;
|
@@ -1,44 +0,0 @@
|
|
1
|
-
import React, { Component } from 'react';
|
2
|
-
import { FormControl } from 'patternfly-react';
|
3
|
-
|
4
|
-
import { commonInputPropTypes } from '../helpers/commonPropTypes';
|
5
|
-
|
6
|
-
class TypeAheadInput extends Component {
|
7
|
-
constructor(props) {
|
8
|
-
super(props);
|
9
|
-
this.handleKeyPress = this.handleKeyPress.bind(this);
|
10
|
-
}
|
11
|
-
|
12
|
-
componentDidMount() {
|
13
|
-
if (this.ref) {
|
14
|
-
this.ref.addEventListener('keydown', this.handleKeyPress);
|
15
|
-
}
|
16
|
-
}
|
17
|
-
|
18
|
-
componentWillUnmount() {
|
19
|
-
if (this.ref) {
|
20
|
-
this.ref.removeEventListener('keydown', this.handleKeyPress);
|
21
|
-
}
|
22
|
-
}
|
23
|
-
|
24
|
-
handleKeyPress(e) {
|
25
|
-
this.props.onKeyPress(e);
|
26
|
-
}
|
27
|
-
|
28
|
-
render() {
|
29
|
-
return (
|
30
|
-
<FormControl
|
31
|
-
inputRef={(ref) => {
|
32
|
-
this.ref = ref;
|
33
|
-
}}
|
34
|
-
onFocus={this.props.onInputFocus}
|
35
|
-
type="text"
|
36
|
-
{...this.props.passedProps}
|
37
|
-
/>
|
38
|
-
);
|
39
|
-
}
|
40
|
-
}
|
41
|
-
|
42
|
-
TypeAheadInput.propTypes = commonInputPropTypes;
|
43
|
-
|
44
|
-
export default TypeAheadInput;
|