Skip to content

Commit 1bd9f0f

Browse files
authored
Merge pull request #9404 from GilbertCherrie/add_entry_point_components
Add embedded automate and workflow entry point UI components
2 parents f69d2fd + 6b36544 commit 1bd9f0f

File tree

43 files changed

+990
-13
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+990
-13
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React, { useState } from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
const Checkbox = ({ defaultState = false, onChange, children }) => {
5+
const [on, setOn] = useState(defaultState);
6+
const handleChange = (e) => {
7+
setOn(e.target.checked);
8+
onChange(e.target.checked);
9+
};
10+
return (
11+
<form style={{ display: 'inline-block', padding: 10 }}>
12+
<label htmlFor="checkbox">
13+
<input type="checkbox" checked={on} onChange={handleChange} />
14+
{children}
15+
</label>
16+
</form>
17+
);
18+
};
19+
20+
Checkbox.propTypes = {
21+
defaultState: PropTypes.bool,
22+
onChange: PropTypes.func.isRequired,
23+
children: PropTypes.arrayOf(PropTypes.any),
24+
};
25+
26+
Checkbox.defaultProps = {
27+
defaultState: false,
28+
children: [],
29+
};
30+
31+
export default Checkbox;
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
import React, { useEffect, useState } from 'react';
2+
import {
3+
Checkbox, Loading, Modal, ModalBody,
4+
} from 'carbon-components-react';
5+
import PropTypes from 'prop-types';
6+
import {
7+
Document16,
8+
Folder16,
9+
FolderOpen16,
10+
} from '@carbon/icons-react';
11+
import TreeView from 'react-accessible-treeview';
12+
import './styles.css';
13+
14+
const initialData = [
15+
{
16+
name: 'Datastore',
17+
id: 'datastore_folder',
18+
children: [],
19+
parent: null,
20+
},
21+
];
22+
23+
const AutomateEntryPoints = ({
24+
selectedValue, showModal, includeDomainPrefix, setSelectedValue, setShowModal, setIncludeDomainPrefix,
25+
}) => {
26+
const [data, setData] = useState(initialData);
27+
const [isLoading, setIsLoading] = useState(true);
28+
const [nodesAlreadyLoaded, setNodesAlreadyLoaded] = useState([]);
29+
const [disableSubmit, setDisableSubmit] = useState(true);
30+
const [selectedNode, setSelectedNode] = useState();
31+
32+
const updateTreeData = (list, id, children) => {
33+
const data = list.map((node) => {
34+
if (node.id === id) {
35+
node.children = children.map((el) => el.id);
36+
}
37+
return node;
38+
});
39+
return data.concat(children);
40+
};
41+
42+
useEffect(() => {
43+
if (selectedValue.element) {
44+
data.forEach((node) => {
45+
if (node.id === selectedValue.element.id) {
46+
document.getElementById(node.id).classList.add('currently-selected');
47+
document.getElementById(node.id).style.backgroundColor = 'rgba(0, 0, 0, 0.2)';
48+
}
49+
});
50+
}
51+
}, [selectedValue]);
52+
53+
useEffect(() => {
54+
miqSparkleOn();
55+
const newChildren = [];
56+
API.get('/api/automate_domains?expand=resources').then((apiData) => {
57+
apiData.resources.forEach((domain) => {
58+
newChildren.push({
59+
id: domain.id,
60+
name: domain.name,
61+
children: [],
62+
parent: 'datastore_folder',
63+
metadata: { parent: domain.name },
64+
isBranch: true,
65+
});
66+
});
67+
return newChildren;
68+
}).then((newChildren) => new Promise((resolve) => {
69+
setTimeout(() => {
70+
setData((value) => updateTreeData(value, 'datastore_folder', newChildren));
71+
resolve();
72+
}, 1000);
73+
})).then(() => {
74+
setIsLoading(false);
75+
miqSparkleOff();
76+
});
77+
}, []);
78+
79+
const onLoadData = ({ element }) => {
80+
let path = element.name;
81+
if (element.metadata && element.metadata.fqname) {
82+
path = element.metadata.fqname;
83+
}
84+
API.get(`/api/automate/${path}?depth=1`).then((newNodes) => {
85+
const newChildren = [];
86+
newNodes.resources.forEach((newNode) => {
87+
if (element.id !== newNode.id) {
88+
if (newNode.klass !== 'MiqAeClass') {
89+
newChildren.push({
90+
id: newNode.id,
91+
name: newNode.name,
92+
children: [],
93+
isBranch: true,
94+
parent: element.id,
95+
metadata: { domain_fqname: newNode.domain_fqname, fqname: newNode.fqname },
96+
});
97+
} else {
98+
newChildren.push({
99+
id: newNode.id,
100+
name: newNode.name,
101+
children: [],
102+
parent: element.id,
103+
metadata: { domain_fqname: newNode.domain_fqname, fqname: newNode.fqname },
104+
});
105+
}
106+
}
107+
});
108+
return new Promise((resolve) => {
109+
if (element.children.length > 0) {
110+
resolve();
111+
return;
112+
}
113+
setTimeout(() => {
114+
setData((value) =>
115+
updateTreeData(value, element.id, newChildren));
116+
resolve();
117+
}, 1000);
118+
});
119+
});
120+
};
121+
122+
const wrappedOnLoadData = async({ element }) => {
123+
const nodeHasNoChildData = element.children.length === 0;
124+
const nodeHasAlreadyBeenLoaded = nodesAlreadyLoaded.find(
125+
(e) => e.id === element.id
126+
);
127+
128+
await onLoadData({ element });
129+
130+
if (nodeHasNoChildData && !nodeHasAlreadyBeenLoaded) {
131+
setNodesAlreadyLoaded([...nodesAlreadyLoaded, element]);
132+
133+
setTimeout(() => {
134+
}, 1000);
135+
}
136+
};
137+
wrappedOnLoadData.propTypes = {
138+
element: PropTypes.objectOf({ children: PropTypes.array, id: PropTypes.number }).isRequired,
139+
};
140+
wrappedOnLoadData.defaultProps = {
141+
};
142+
143+
const onSelect = (value) => {
144+
if (value.isBranch === false && value.isSelected) {
145+
data.forEach((node) => {
146+
if (selectedNode && (node.id === selectedNode.element.id)) {
147+
document.getElementById(node.id).style.backgroundColor = 'transparent';
148+
}
149+
});
150+
document.getElementById(value.element.id).style.backgroundColor = 'rgba(0, 0, 0, 0.2)';
151+
setSelectedNode(value);
152+
setDisableSubmit(false);
153+
}
154+
};
155+
156+
const onExpand = (value) => {
157+
if (value.isExpanded && selectedNode && document.getElementById(selectedNode.element.id)) {
158+
document.getElementById(selectedNode.element.id).style.backgroundColor = 'rgba(0, 0, 0, 0.2)';
159+
}
160+
};
161+
162+
const FolderIcon = ({ isOpen }) =>
163+
(isOpen ? (
164+
<FolderOpen16 className="icon" />
165+
) : (
166+
<Folder16 className="icon" />
167+
));
168+
169+
FolderIcon.propTypes = {
170+
isOpen: PropTypes.bool,
171+
};
172+
FolderIcon.defaultProps = {
173+
isOpen: false,
174+
};
175+
176+
const FileIcon = () => <Document16 className="icon" />;
177+
178+
return !isLoading && (
179+
<Modal
180+
open={showModal}
181+
primaryButtonText={__('OK')}
182+
secondaryButtonText={__('Cancel')}
183+
onRequestSubmit={() => {
184+
setSelectedValue(selectedNode);
185+
setShowModal(false);
186+
}}
187+
onRequestClose={() => {
188+
setShowModal(false);
189+
}}
190+
onSecondarySubmit={() => {
191+
setShowModal(false);
192+
}}
193+
primaryButtonDisabled={disableSubmit}
194+
>
195+
<ModalBody>
196+
<div>
197+
<div className="automate_entry_points">
198+
<TreeView
199+
data={data}
200+
aria-label="Automate Entry Points tree"
201+
onSelect={onSelect}
202+
onLoadData={wrappedOnLoadData}
203+
onExpand={onExpand}
204+
togglableSelect
205+
nodeRenderer={({
206+
element,
207+
isBranch,
208+
isExpanded,
209+
getNodeProps,
210+
level,
211+
}) => {
212+
const branchNode = (isExpanded, element) => (isExpanded && element.children.length === 0 ? (
213+
<div className="loadingDiv">
214+
<Loading small withOverlay={false} />
215+
</div>
216+
) : (
217+
null
218+
));
219+
return (
220+
<div
221+
{...getNodeProps()}
222+
style={{ marginLeft: 40 * (level - 1) }}
223+
id={element.id}
224+
>
225+
{isBranch && branchNode(isExpanded, element)}
226+
{isBranch ? (
227+
<div className="iconDiv">
228+
<FolderIcon isOpen={isExpanded} />
229+
</div>
230+
) : (
231+
<div className="iconDiv">
232+
<FileIcon filename={element.name} />
233+
</div>
234+
)}
235+
<span className="name">{element.name}</span>
236+
</div>
237+
);
238+
}}
239+
/>
240+
</div>
241+
{setIncludeDomainPrefix
242+
? (
243+
<div className="checkboxDiv">
244+
<Checkbox
245+
id="includeDomainPrefix"
246+
labelText={__('Include Domain prefix in the path')}
247+
checked={includeDomainPrefix}
248+
onChange={(checked) => setIncludeDomainPrefix(checked)}
249+
/>
250+
</div>
251+
) : null}
252+
</div>
253+
</ModalBody>
254+
</Modal>
255+
);
256+
};
257+
258+
AutomateEntryPoints.propTypes = {
259+
field: PropTypes.string.isRequired,
260+
type: PropTypes.string.isRequired,
261+
selected: PropTypes.string,
262+
selectedValue: PropTypes.objectOf(PropTypes.any),
263+
showModal: PropTypes.bool,
264+
includeDomainPrefix: PropTypes.bool,
265+
setSelectedValue: PropTypes.func.isRequired,
266+
setShowModal: PropTypes.func.isRequired,
267+
setIncludeDomainPrefix: PropTypes.func,
268+
};
269+
270+
AutomateEntryPoints.defaultProps = {
271+
selected: '',
272+
selectedValue: {},
273+
showModal: false,
274+
includeDomainPrefix: false,
275+
setIncludeDomainPrefix: undefined,
276+
};
277+
278+
export default AutomateEntryPoints;
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
.iconDiv {
2+
display: inline-flex;
3+
margin-top: 5px;
4+
margin-right: 5px;
5+
}
6+
7+
.loadingDiv {
8+
display: inline-flex;
9+
margin-right: 1rem;
10+
}
11+
12+
.automate_entry_points {
13+
font-size: 16px;
14+
user-select: none;
15+
height: 250px;
16+
overflow-y: scroll;
17+
padding: 20px;
18+
margin-bottom: 10px;
19+
box-sizing: content-box;
20+
}
21+
22+
.automate_entry_points .tree,
23+
.automate_entry_points .tree-node,
24+
.automate_entry_points .tree-node-group {
25+
list-style: none;
26+
margin: 0;
27+
padding: 0;
28+
}
29+
30+
.automate_entry_points .tree-branch-wrapper,
31+
.automate_entry_points .tree-node__leaf {
32+
outline: none;
33+
}
34+
35+
.automate_entry_points .tree-node {
36+
cursor: pointer;
37+
}
38+
39+
.automate_entry_points .tree-node .name:hover {
40+
background: rgba(0, 0, 0, 0.1);
41+
background-color: transparent;
42+
}
43+
44+
.automate_entry_points .tree-node--focused .name {
45+
background: rgba(0, 0, 0, 0.2);
46+
background-color: transparent;
47+
}
48+
49+
.automate_entry_points .tree-node {
50+
display: inline-block;
51+
}
52+
53+
.automate_entry_points .checkbox-icon {
54+
margin: 0 5px;
55+
vertical-align: middle;
56+
}
57+
58+
.automate_entry_points button {
59+
border: none;
60+
background: transparent;
61+
cursor: pointer;
62+
}
63+
64+
.automate_entry_points .arrow {
65+
margin-left: 5px;
66+
vertical-align: middle;
67+
}
68+
69+
.automate_entry_points .arrow--open {
70+
transform: rotate(270deg);
71+
}
72+
73+
.bx--modal-content {
74+
margin-bottom: 1rem;
75+
}
76+
77+
.bx--btn--primary {
78+
margin-right: 10px;
79+
}
80+
81+
.checkboxDiv {
82+
float: right;
83+
}

0 commit comments

Comments
 (0)