foreman_scc_manager 1.8.20 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +14 -10
- data/app/controllers/api/v2/scc_accounts_controller.rb +47 -4
- data/app/controllers/api/v2/scc_products_controller.rb +1 -1
- data/app/controllers/scc_accounts_controller.rb +47 -0
- data/app/lib/actions/scc_manager/subscribe_product.rb +51 -15
- data/app/models/scc_account.rb +1 -1
- data/app/views/scc_accounts/show.html.erb +7 -51
- data/config/routes.rb +1 -0
- data/db/migrate/20220429102717_populate_scc_katello_repositories.rb +5 -0
- data/db/migrate/20220531120722_add_product_category_to_scc_products.rb +5 -0
- data/lib/foreman_scc_manager/engine.rb +1 -1
- data/lib/foreman_scc_manager/version.rb +1 -1
- data/lib/tasks/republish_repositories.rake +13 -0
- data/package.json +54 -0
- data/test/controllers/scc_accounts_controller_test.rb +6 -0
- data/webpack/components/SCCProductPage/EmptySccProducts.js +46 -0
- data/webpack/components/SCCProductPage/SCCProductPage.js +96 -0
- data/webpack/components/SCCProductPage/SCCProductPageActions.js +27 -0
- data/webpack/components/SCCProductPage/SCCProductPageConstants.js +5 -0
- data/webpack/components/SCCProductPage/SCCProductPageReducer.js +34 -0
- data/webpack/components/SCCProductPage/SCCProductPageSelectors.js +7 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCGenericPicker/index.js +80 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/components/SCCRepoPicker/index.js +174 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/components/SCCRepoPicker/styles.scss +3 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/index.js +303 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/index.js +235 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/styles.scss +10 -0
- data/webpack/components/SCCProductPage/components/SCCProductPickerModal/index.js +81 -0
- data/webpack/components/SCCProductPage/components/SCCProductView/components/SCCRepoView/index.js +113 -0
- data/webpack/components/SCCProductPage/components/SCCProductView/components/SCCRepoView/styles.scss +14 -0
- data/webpack/components/SCCProductPage/components/SCCProductView/index.js +236 -0
- data/webpack/components/SCCProductPage/components/common/SCCGenericExpander/index.js +58 -0
- data/webpack/components/SCCProductPage/components/common/SCCProductTreeExpander/index.js +21 -0
- data/webpack/components/SCCProductPage/components/common/SCCSubscribedProductsExpander/index.js +21 -0
- data/webpack/components/SCCProductPage/index.js +18 -0
- data/webpack/components/SCCProductPage/sccProductPage.scss +8 -0
- data/webpack/index.js +11 -0
- data/webpack/reducer.js +7 -0
- metadata +40 -14
@@ -0,0 +1,27 @@
|
|
1
|
+
import { API_OPERATIONS, put } from 'foremanReact/redux/API';
|
2
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
3
|
+
|
4
|
+
export const subscribeProductsWithReposAction = (
|
5
|
+
sccAccountId,
|
6
|
+
sccProductData,
|
7
|
+
handleSubscription,
|
8
|
+
sccSubscriptionData
|
9
|
+
) =>
|
10
|
+
put({
|
11
|
+
type: API_OPERATIONS.PUT,
|
12
|
+
key: `subscribe_key_${sccAccountId}_${sccProductData[0].scc_product_id}_${sccProductData[0].repository_list}`,
|
13
|
+
url: `/api/scc_accounts/${sccAccountId}/bulk_subscribe_with_repos`,
|
14
|
+
params: { scc_product_data: sccProductData },
|
15
|
+
errorToast: (error) => __('Starting the subscription task failed.'),
|
16
|
+
handleSuccess: (response) =>
|
17
|
+
handleSubscription(response.data.id, sccSubscriptionData),
|
18
|
+
});
|
19
|
+
|
20
|
+
export const syncSccAccountAction = (sccAccountId) =>
|
21
|
+
put({
|
22
|
+
type: API_OPERATIONS.PUT,
|
23
|
+
key: `syncSccAccountKey_${sccAccountId}`,
|
24
|
+
url: `/api/scc_accounts/${sccAccountId}/sync`,
|
25
|
+
successToast: () => __('Sync task started.'),
|
26
|
+
errorToast: (error) => __('Failed to add task to queue.'),
|
27
|
+
});
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import Immutable from 'seamless-immutable';
|
2
|
+
import { actionTypeGenerator } from 'foremanReact/redux/API';
|
3
|
+
|
4
|
+
export const initialState = Immutable({
|
5
|
+
sccProducts: [],
|
6
|
+
sccAccountId: undefined,
|
7
|
+
});
|
8
|
+
|
9
|
+
export default (state = initialState, action) => {
|
10
|
+
const { payload } = action;
|
11
|
+
|
12
|
+
const listTypes = actionTypeGenerator('SCC_PRODUCT_LIST');
|
13
|
+
|
14
|
+
switch (action.type) {
|
15
|
+
case 'FETCH_PRODUCT_SUCCESS':
|
16
|
+
return state.merge({
|
17
|
+
sccProducts: payload,
|
18
|
+
});
|
19
|
+
case listTypes.REQUEST:
|
20
|
+
return state.merge({
|
21
|
+
sccProducts: [],
|
22
|
+
});
|
23
|
+
case listTypes.SUCCESS:
|
24
|
+
return state.merge({
|
25
|
+
sccProducts: payload.data,
|
26
|
+
});
|
27
|
+
case listTypes.FAILURE:
|
28
|
+
return state.merge({
|
29
|
+
sccProducts: [payload.error],
|
30
|
+
});
|
31
|
+
default:
|
32
|
+
return state;
|
33
|
+
}
|
34
|
+
};
|
@@ -0,0 +1,80 @@
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { ContextSelector, ContextSelectorItem } from '@patternfly/react-core';
|
4
|
+
|
5
|
+
const GenericSelector = ({
|
6
|
+
selectionItems,
|
7
|
+
setGlobalSelected,
|
8
|
+
screenReaderLabel,
|
9
|
+
initialLabel,
|
10
|
+
}) => {
|
11
|
+
const [isOpen, setIsOpen] = useState(false);
|
12
|
+
const [selected, setSelected] = useState(initialLabel);
|
13
|
+
const [searchValue, setSearchValue] = useState('');
|
14
|
+
const [filteredItems, setFilteredItems] = useState(selectionItems);
|
15
|
+
|
16
|
+
useEffect(() => {
|
17
|
+
setFilteredItems(selectionItems);
|
18
|
+
setSelected(initialLabel);
|
19
|
+
}, [selectionItems, initialLabel]);
|
20
|
+
|
21
|
+
const onToggle = (selectorOpen) => {
|
22
|
+
setIsOpen(selectorOpen);
|
23
|
+
};
|
24
|
+
|
25
|
+
const onSelect = (event, value) => {
|
26
|
+
setSelected(value);
|
27
|
+
setIsOpen(!isOpen);
|
28
|
+
setGlobalSelected(value);
|
29
|
+
};
|
30
|
+
|
31
|
+
const onSearchInputChange = (value) => {
|
32
|
+
setSearchValue(value);
|
33
|
+
};
|
34
|
+
|
35
|
+
const onSearchButtonClick = (event) => {
|
36
|
+
const filtered =
|
37
|
+
searchValue === ''
|
38
|
+
? selectionItems
|
39
|
+
: selectionItems.filter((item) => {
|
40
|
+
const str = item.text ? item.text : item;
|
41
|
+
return str.toLowerCase().indexOf(searchValue.toLowerCase()) !== -1;
|
42
|
+
});
|
43
|
+
|
44
|
+
setFilteredItems(filtered || []);
|
45
|
+
};
|
46
|
+
|
47
|
+
return (
|
48
|
+
<ContextSelector
|
49
|
+
toggleText={selected}
|
50
|
+
onSearchInputChange={onSearchInputChange}
|
51
|
+
isOpen={isOpen}
|
52
|
+
searchInputValue={searchValue}
|
53
|
+
onToggle={onToggle}
|
54
|
+
onSelect={onSelect}
|
55
|
+
onSearchButtonClick={onSearchButtonClick}
|
56
|
+
screenReaderLabel={screenReaderLabel}
|
57
|
+
>
|
58
|
+
{filteredItems.map((item, index) => (
|
59
|
+
<ContextSelectorItem key={index}>
|
60
|
+
{item || 'no arch'}
|
61
|
+
</ContextSelectorItem>
|
62
|
+
))}
|
63
|
+
</ContextSelector>
|
64
|
+
);
|
65
|
+
};
|
66
|
+
|
67
|
+
GenericSelector.propTypes = {
|
68
|
+
selectionItems: PropTypes.array,
|
69
|
+
setGlobalSelected: PropTypes.func.isRequired,
|
70
|
+
screenReaderLabel: PropTypes.string,
|
71
|
+
initialLabel: PropTypes.string,
|
72
|
+
};
|
73
|
+
|
74
|
+
GenericSelector.defaultProps = {
|
75
|
+
selectionItems: [],
|
76
|
+
screenReaderLabel: '',
|
77
|
+
initialLabel: '',
|
78
|
+
};
|
79
|
+
|
80
|
+
export default GenericSelector;
|
@@ -0,0 +1,174 @@
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { sprintf, translate as __ } from 'foremanReact/common/I18n';
|
4
|
+
import { Select, SelectOption, SelectVariant } from '@patternfly/react-core';
|
5
|
+
|
6
|
+
import './styles.scss';
|
7
|
+
|
8
|
+
const createRepoSelectOption = (repo, disableRepos) => (
|
9
|
+
<SelectOption
|
10
|
+
key={repo.id}
|
11
|
+
isDisabled={repo.katello_root_repository_id !== null || disableRepos}
|
12
|
+
value={repo.name}
|
13
|
+
/>
|
14
|
+
);
|
15
|
+
|
16
|
+
const setRepoSelection = (
|
17
|
+
sccRepos,
|
18
|
+
disableRepos,
|
19
|
+
activateDebugFilter,
|
20
|
+
productAlreadySynced
|
21
|
+
) => {
|
22
|
+
let res = [];
|
23
|
+
// this is necessary because the logic of this option was inverted
|
24
|
+
// Instead of filtering the debug repositories with the corresponding option,
|
25
|
+
// this option now includes the repositories, instead - means, it does exactly the opposite.
|
26
|
+
activateDebugFilter = !activateDebugFilter;
|
27
|
+
if (!disableRepos && !productAlreadySynced) {
|
28
|
+
if (activateDebugFilter) {
|
29
|
+
res = sccRepos.filter(
|
30
|
+
(repo) =>
|
31
|
+
(!repo.name.includes('Debug') &&
|
32
|
+
!repo.name.includes('Source-Pool') &&
|
33
|
+
repo.katello_root_repository_id === null) ||
|
34
|
+
repo.katello_root_repository_id !== null
|
35
|
+
);
|
36
|
+
} else {
|
37
|
+
res = sccRepos;
|
38
|
+
}
|
39
|
+
} else {
|
40
|
+
res = sccRepos.filter((repo) => repo.katello_root_repository_id !== null);
|
41
|
+
}
|
42
|
+
return res.map((repo) => repo.name);
|
43
|
+
};
|
44
|
+
|
45
|
+
// disableRepos makes sure that repos can only be selected if the corresponding product
|
46
|
+
// is also selected
|
47
|
+
const SCCRepoPicker = ({
|
48
|
+
sccRepos,
|
49
|
+
disableRepos,
|
50
|
+
activateDebugFilter,
|
51
|
+
productAlreadySynced,
|
52
|
+
sccProductId,
|
53
|
+
sccProductName,
|
54
|
+
setSelectedReposFromChild,
|
55
|
+
}) => {
|
56
|
+
const [isOpen, setIsOpen] = useState(false);
|
57
|
+
// set initial selected values to the already checked repos
|
58
|
+
const [selected, setSelected] = useState(
|
59
|
+
setRepoSelection(
|
60
|
+
sccRepos,
|
61
|
+
disableRepos,
|
62
|
+
activateDebugFilter,
|
63
|
+
productAlreadySynced
|
64
|
+
)
|
65
|
+
);
|
66
|
+
const onToggle = (toggle) => {
|
67
|
+
setIsOpen(toggle);
|
68
|
+
};
|
69
|
+
|
70
|
+
useEffect(() => {
|
71
|
+
const selectedRepos = setRepoSelection(
|
72
|
+
sccRepos,
|
73
|
+
disableRepos,
|
74
|
+
activateDebugFilter,
|
75
|
+
productAlreadySynced
|
76
|
+
);
|
77
|
+
setSelected(selectedRepos);
|
78
|
+
setSelectedReposFromChild(
|
79
|
+
sccProductId,
|
80
|
+
sccProductName,
|
81
|
+
sccRepos
|
82
|
+
// make sure that we do not request already subscribed repositories
|
83
|
+
.filter(
|
84
|
+
(repo) =>
|
85
|
+
selectedRepos.includes(repo.name) &&
|
86
|
+
repo.katello_root_repository_id === null
|
87
|
+
)
|
88
|
+
.map((repo) => repo.id),
|
89
|
+
sccRepos
|
90
|
+
// make sure that we do not request already subscribed repositories
|
91
|
+
.filter(
|
92
|
+
(repo) =>
|
93
|
+
selectedRepos.includes(repo.name) &&
|
94
|
+
repo.katello_root_repository_id === null
|
95
|
+
)
|
96
|
+
.map((repo) => repo.name)
|
97
|
+
);
|
98
|
+
}, [
|
99
|
+
sccRepos,
|
100
|
+
disableRepos,
|
101
|
+
activateDebugFilter,
|
102
|
+
productAlreadySynced,
|
103
|
+
sccProductId,
|
104
|
+
setSelectedReposFromChild,
|
105
|
+
]);
|
106
|
+
|
107
|
+
const onSelect = (event, selection) => {
|
108
|
+
let selectedRepos = [];
|
109
|
+
if (event.target.checked) {
|
110
|
+
selectedRepos = [...new Set(selected.concat([selection]))];
|
111
|
+
} else {
|
112
|
+
selectedRepos = selected.filter((item) => item !== selection);
|
113
|
+
}
|
114
|
+
setSelected(selectedRepos);
|
115
|
+
setSelectedReposFromChild(
|
116
|
+
sccProductId,
|
117
|
+
sccProductName,
|
118
|
+
sccRepos
|
119
|
+
// make sure that we do not request already subscribed repositories
|
120
|
+
.filter(
|
121
|
+
(repo) =>
|
122
|
+
selectedRepos.includes(repo.name) &&
|
123
|
+
repo.katello_root_repository_id === null
|
124
|
+
)
|
125
|
+
.map((repo) => repo.id),
|
126
|
+
sccRepos
|
127
|
+
// make sure that we do not request already subscribed repositories
|
128
|
+
.filter(
|
129
|
+
(repo) =>
|
130
|
+
selectedRepos.includes(repo.name) &&
|
131
|
+
repo.katello_root_repository_id === null
|
132
|
+
)
|
133
|
+
.map((repo) => repo.name)
|
134
|
+
);
|
135
|
+
};
|
136
|
+
|
137
|
+
const selectOptions = sccRepos.map((repo) =>
|
138
|
+
createRepoSelectOption(repo, disableRepos)
|
139
|
+
);
|
140
|
+
|
141
|
+
return (
|
142
|
+
<Select
|
143
|
+
variant={SelectVariant.checkbox}
|
144
|
+
isCheckboxSelectionBadgeHidden
|
145
|
+
onToggle={onToggle}
|
146
|
+
onSelect={onSelect}
|
147
|
+
selections={selected}
|
148
|
+
isOpen={isOpen}
|
149
|
+
isDisabled={disableRepos}
|
150
|
+
placeholderText={sprintf(__('%s/%s'), selected.length, sccRepos.length)}
|
151
|
+
>
|
152
|
+
{selectOptions}
|
153
|
+
</Select>
|
154
|
+
);
|
155
|
+
};
|
156
|
+
|
157
|
+
SCCRepoPicker.propTypes = {
|
158
|
+
sccRepos: PropTypes.array,
|
159
|
+
disableRepos: PropTypes.bool,
|
160
|
+
activateDebugFilter: PropTypes.bool,
|
161
|
+
productAlreadySynced: PropTypes.bool,
|
162
|
+
sccProductId: PropTypes.number.isRequired,
|
163
|
+
sccProductName: PropTypes.string.isRequired,
|
164
|
+
setSelectedReposFromChild: PropTypes.func.isRequired,
|
165
|
+
};
|
166
|
+
|
167
|
+
SCCRepoPicker.defaultProps = {
|
168
|
+
sccRepos: [],
|
169
|
+
disableRepos: false,
|
170
|
+
activateDebugFilter: false,
|
171
|
+
productAlreadySynced: false,
|
172
|
+
};
|
173
|
+
|
174
|
+
export default SCCRepoPicker;
|
data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/index.js
ADDED
@@ -0,0 +1,303 @@
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
2
|
+
import { useDispatch } from 'react-redux';
|
3
|
+
import PropTypes from 'prop-types';
|
4
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
5
|
+
import {
|
6
|
+
TreeView,
|
7
|
+
Button,
|
8
|
+
Tooltip,
|
9
|
+
Switch,
|
10
|
+
Flex,
|
11
|
+
FlexItem,
|
12
|
+
Card,
|
13
|
+
CardBody,
|
14
|
+
} from '@patternfly/react-core';
|
15
|
+
import { cloneDeep, merge, clone } from 'lodash';
|
16
|
+
import SCCRepoPicker from './components/SCCRepoPicker';
|
17
|
+
import { subscribeProductsWithReposAction } from '../../../../SCCProductPageActions';
|
18
|
+
import SCCProductTreeExpander from '../../../common/SCCProductTreeExpander';
|
19
|
+
|
20
|
+
const addCheckBoxToTree = (tree) => {
|
21
|
+
const checkProps = {};
|
22
|
+
checkProps.checked = tree.product_id !== null;
|
23
|
+
checkProps.disabled = tree.product_id !== null;
|
24
|
+
tree.checkProps = checkProps;
|
25
|
+
|
26
|
+
return tree;
|
27
|
+
};
|
28
|
+
|
29
|
+
const addParentToTree = (tree, par) => {
|
30
|
+
tree.parent = par;
|
31
|
+
return tree;
|
32
|
+
};
|
33
|
+
|
34
|
+
const addSCCRepoPickerToTree = (
|
35
|
+
tree,
|
36
|
+
disableRepos,
|
37
|
+
activateDebugFilter,
|
38
|
+
setSelectedReposFromChild
|
39
|
+
) => {
|
40
|
+
tree.customBadgeContent[0] = (
|
41
|
+
<Tooltip content={__('Filter repositories')}>
|
42
|
+
<SCCRepoPicker
|
43
|
+
sccRepos={tree.scc_repositories}
|
44
|
+
disableRepos={tree.product_id === null && !tree.checkProps.checked}
|
45
|
+
activateDebugFilter={activateDebugFilter}
|
46
|
+
productAlreadySynced={tree.product_id !== null}
|
47
|
+
sccProductId={tree.id}
|
48
|
+
sccProductName={tree.name}
|
49
|
+
setSelectedReposFromChild={setSelectedReposFromChild}
|
50
|
+
/>{' '}
|
51
|
+
</Tooltip>
|
52
|
+
);
|
53
|
+
return tree;
|
54
|
+
};
|
55
|
+
|
56
|
+
const setupTreeViewListItem = (
|
57
|
+
tree,
|
58
|
+
isRoot,
|
59
|
+
activateDebugFilter,
|
60
|
+
setSelectedReposFromChild
|
61
|
+
) => {
|
62
|
+
tree.customBadgeContent = [];
|
63
|
+
addCheckBoxToTree(tree);
|
64
|
+
addSCCRepoPickerToTree(
|
65
|
+
tree,
|
66
|
+
tree.product_id === null,
|
67
|
+
activateDebugFilter,
|
68
|
+
setSelectedReposFromChild
|
69
|
+
);
|
70
|
+
if ('children' in tree) {
|
71
|
+
tree.children = tree.children.map((p) =>
|
72
|
+
setupTreeViewListItem(
|
73
|
+
p,
|
74
|
+
false,
|
75
|
+
activateDebugFilter,
|
76
|
+
setSelectedReposFromChild
|
77
|
+
)
|
78
|
+
);
|
79
|
+
tree.children.map((child) => addParentToTree(child, tree));
|
80
|
+
}
|
81
|
+
return tree;
|
82
|
+
};
|
83
|
+
|
84
|
+
const checkAllParents = (
|
85
|
+
tree,
|
86
|
+
activateDebugFilter,
|
87
|
+
setSelectedReposFromChild
|
88
|
+
) => {
|
89
|
+
if (!tree.checkProps.checked) {
|
90
|
+
tree.checkProps.checked = true;
|
91
|
+
addSCCRepoPickerToTree(
|
92
|
+
tree,
|
93
|
+
false,
|
94
|
+
activateDebugFilter,
|
95
|
+
setSelectedReposFromChild
|
96
|
+
);
|
97
|
+
}
|
98
|
+
if (tree.parent)
|
99
|
+
checkAllParents(
|
100
|
+
tree.parent,
|
101
|
+
activateDebugFilter,
|
102
|
+
setSelectedReposFromChild
|
103
|
+
);
|
104
|
+
|
105
|
+
return tree;
|
106
|
+
};
|
107
|
+
|
108
|
+
const uncheckAllChildren = (
|
109
|
+
tree,
|
110
|
+
activateDebugFilter,
|
111
|
+
setSelectedReposFromChild
|
112
|
+
) => {
|
113
|
+
if (tree.product_id === null) {
|
114
|
+
tree.checkProps.checked = false;
|
115
|
+
addSCCRepoPickerToTree(
|
116
|
+
tree,
|
117
|
+
true,
|
118
|
+
activateDebugFilter,
|
119
|
+
setSelectedReposFromChild
|
120
|
+
);
|
121
|
+
}
|
122
|
+
if ('children' in tree)
|
123
|
+
tree.children = tree.children.map((c) =>
|
124
|
+
uncheckAllChildren(c, activateDebugFilter, setSelectedReposFromChild)
|
125
|
+
);
|
126
|
+
|
127
|
+
return tree;
|
128
|
+
};
|
129
|
+
|
130
|
+
const getRootParent = (tree) => {
|
131
|
+
if (tree.parent) return getRootParent(tree.parent);
|
132
|
+
|
133
|
+
return tree;
|
134
|
+
};
|
135
|
+
|
136
|
+
const SCCTreePicker = ({
|
137
|
+
sccProducts,
|
138
|
+
sccAccountId,
|
139
|
+
resetFormFromParent,
|
140
|
+
handleSubscribeCallback,
|
141
|
+
}) => {
|
142
|
+
const dispatch = useDispatch();
|
143
|
+
// this needs to be uninitialized such that the first call to setAllExpanded can actually
|
144
|
+
// change the value of allExpanded
|
145
|
+
const [expandAll, setExpandAll] = useState();
|
146
|
+
const [selectedRepos, setSelectedRepos] = useState({});
|
147
|
+
// the debug filter is actually a 'includeDebugRepos' setting which should not be active by default
|
148
|
+
const [activateDebugFilter, setActivateDebugFilter] = useState(false);
|
149
|
+
|
150
|
+
const setSelectedReposFromChild = (
|
151
|
+
productId,
|
152
|
+
productName,
|
153
|
+
repoIds,
|
154
|
+
repoNames
|
155
|
+
) => {
|
156
|
+
if (repoIds.length !== 0) {
|
157
|
+
selectedRepos[productId] = {};
|
158
|
+
selectedRepos[productId].repoIds = repoIds;
|
159
|
+
selectedRepos[productId].productName = productName;
|
160
|
+
selectedRepos[productId].repoNames = repoNames;
|
161
|
+
const newSelectedRepos = clone(selectedRepos);
|
162
|
+
setSelectedRepos(newSelectedRepos);
|
163
|
+
} else if (selectedRepos !== {} && productId in selectedRepos) {
|
164
|
+
delete selectedRepos[productId];
|
165
|
+
const newSelectedRepos = clone(selectedRepos);
|
166
|
+
setSelectedRepos(newSelectedRepos);
|
167
|
+
}
|
168
|
+
};
|
169
|
+
|
170
|
+
const [sccProductTree, setSccProductTree] = useState(
|
171
|
+
cloneDeep(sccProducts).map((p) =>
|
172
|
+
setupTreeViewListItem(
|
173
|
+
p,
|
174
|
+
true,
|
175
|
+
activateDebugFilter,
|
176
|
+
setSelectedReposFromChild
|
177
|
+
)
|
178
|
+
)
|
179
|
+
);
|
180
|
+
|
181
|
+
useEffect(() => {
|
182
|
+
setSccProductTree(
|
183
|
+
cloneDeep(sccProducts).map((p) =>
|
184
|
+
setupTreeViewListItem(
|
185
|
+
p,
|
186
|
+
true,
|
187
|
+
activateDebugFilter,
|
188
|
+
setSelectedReposFromChild
|
189
|
+
)
|
190
|
+
)
|
191
|
+
);
|
192
|
+
// some thorough cleaning is required for hash maps
|
193
|
+
Object.keys(selectedRepos).forEach((k) => delete selectedRepos[k]);
|
194
|
+
}, [sccProducts]);
|
195
|
+
|
196
|
+
const setExpandAllFromChild = (expandAllFromChild) => {
|
197
|
+
setExpandAll(expandAllFromChild);
|
198
|
+
};
|
199
|
+
|
200
|
+
const debugFilterChange = (evt) => {
|
201
|
+
setActivateDebugFilter(!activateDebugFilter);
|
202
|
+
};
|
203
|
+
|
204
|
+
const onCheck = (evt, treeViewItem) => {
|
205
|
+
if (evt.target.checked) {
|
206
|
+
checkAllParents(
|
207
|
+
treeViewItem,
|
208
|
+
activateDebugFilter,
|
209
|
+
setSelectedReposFromChild
|
210
|
+
);
|
211
|
+
} else {
|
212
|
+
uncheckAllChildren(
|
213
|
+
treeViewItem,
|
214
|
+
activateDebugFilter,
|
215
|
+
setSelectedReposFromChild
|
216
|
+
);
|
217
|
+
}
|
218
|
+
|
219
|
+
setSccProductTree([...merge(sccProductTree, getRootParent(treeViewItem))]);
|
220
|
+
};
|
221
|
+
|
222
|
+
const submitForm = (evt) => {
|
223
|
+
const productsToSubscribe = [];
|
224
|
+
Object.keys(selectedRepos).forEach((k) => {
|
225
|
+
const repo = {
|
226
|
+
scc_product_id: parseInt(k, 10),
|
227
|
+
repository_list: selectedRepos[k].repoIds,
|
228
|
+
};
|
229
|
+
productsToSubscribe.push(repo);
|
230
|
+
});
|
231
|
+
dispatch(
|
232
|
+
subscribeProductsWithReposAction(
|
233
|
+
sccAccountId,
|
234
|
+
productsToSubscribe,
|
235
|
+
handleSubscribeCallback,
|
236
|
+
selectedRepos
|
237
|
+
)
|
238
|
+
);
|
239
|
+
// reset data structure and form
|
240
|
+
setSelectedRepos({});
|
241
|
+
resetFormFromParent();
|
242
|
+
};
|
243
|
+
|
244
|
+
return (
|
245
|
+
<Card>
|
246
|
+
<CardBody>
|
247
|
+
<Flex direction={{ default: 'column' }}>
|
248
|
+
<Flex>
|
249
|
+
<SCCProductTreeExpander
|
250
|
+
setExpandAllInParent={setExpandAllFromChild}
|
251
|
+
/>
|
252
|
+
<FlexItem>
|
253
|
+
<Tooltip
|
254
|
+
content={__(
|
255
|
+
'If this option is enabled, debug and source pool repositories are automatically selected if you select a product. This option is disabled by default. It applies for unselected products, only. Already selected products are not filtered.'
|
256
|
+
)}
|
257
|
+
>
|
258
|
+
<Switch
|
259
|
+
id="filter-debug-switch"
|
260
|
+
onChange={debugFilterChange}
|
261
|
+
isChecked={activateDebugFilter}
|
262
|
+
label={__('Include Debug and Source Pool repositories')}
|
263
|
+
/>
|
264
|
+
</Tooltip>
|
265
|
+
</FlexItem>
|
266
|
+
</Flex>
|
267
|
+
<Flex>
|
268
|
+
<TreeView
|
269
|
+
data={sccProductTree}
|
270
|
+
allExpanded={expandAll}
|
271
|
+
onCheck={onCheck}
|
272
|
+
hasChecks
|
273
|
+
hasBadges
|
274
|
+
hasGuides
|
275
|
+
/>
|
276
|
+
</Flex>
|
277
|
+
<Flex>
|
278
|
+
<Button
|
279
|
+
variant="primary"
|
280
|
+
onClick={submitForm}
|
281
|
+
isDisabled={Object.keys(selectedRepos).length === 0}
|
282
|
+
>
|
283
|
+
{__('Add product(s)')}
|
284
|
+
</Button>
|
285
|
+
</Flex>
|
286
|
+
</Flex>
|
287
|
+
</CardBody>
|
288
|
+
</Card>
|
289
|
+
);
|
290
|
+
};
|
291
|
+
|
292
|
+
SCCTreePicker.propTypes = {
|
293
|
+
sccProducts: PropTypes.array,
|
294
|
+
sccAccountId: PropTypes.number.isRequired,
|
295
|
+
resetFormFromParent: PropTypes.func.isRequired,
|
296
|
+
handleSubscribeCallback: PropTypes.func.isRequired,
|
297
|
+
};
|
298
|
+
|
299
|
+
SCCTreePicker.defaultProps = {
|
300
|
+
sccProducts: [],
|
301
|
+
};
|
302
|
+
|
303
|
+
export default SCCTreePicker;
|