foreman_scc_manager 1.8.20 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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;
|